What Causes This Error?
This error occurs when you try to use await or async operations inside definePageMeta(). Page meta must be statically analyzable at build time, so dynamic async operations aren’t allowed.
The Problem
<script setup>
// ❌ Wrong - await inside definePageMeta
definePageMeta({
title: await fetchTitle(), // Error!
middleware: await getMiddleware() // Error!
})
// ❌ Wrong - async function call
definePageMeta({
validate: async () => {
const valid = await checkPermission() // This specific case might work
return valid
}
})
</script>
The Fix
Use Static Values
<script setup>
// ✅ Correct - static values only
definePageMeta({
title: 'My Page',
layout: 'default',
middleware: ['auth']
})
</script>
Move Dynamic Logic Elsewhere
<script setup>
// ✅ Static page meta
definePageMeta({
middleware: 'auth',
layout: 'dashboard'
})
// ✅ Dynamic data fetched separately
const { data: pageData } = await useFetch('/api/page-config')
// ✅ Dynamic head using useHead
useHead({
title: computed(() => pageData.value?.title || 'Default Title')
})
</script>
What Can Go in definePageMeta
Static Strings
<script setup>
definePageMeta({
title: 'Products', // ✅ Static string
layout: 'shop', // ✅ Static string
name: 'products-page' // ✅ Static string
})
</script>
Static Arrays
<script setup>
definePageMeta({
middleware: ['auth', 'admin'], // ✅ Static array
alias: ['/items', '/goods'] // ✅ Static array
})
</script>
Simple Objects
<script setup>
definePageMeta({
// ✅ Static object with primitive values
auth: {
required: true,
roles: ['admin', 'editor']
}
})
</script>
Sync Validation Function
<script setup>
definePageMeta({
// ✅ Validate CAN be async (special case)
validate: async (route) => {
const id = Number(route.params.id)
return !isNaN(id) && id > 0
}
})
</script>
What Cannot Go in definePageMeta
Async Operations
<script setup>
// ❌ All of these are wrong
definePageMeta({
title: await getTitle(),
middleware: await fetchMiddleware(),
layout: await determineLayout()
})
</script>
Reactive Values
<script setup>
const dynamicLayout = ref('default')
// ❌ Wrong - reactive value
definePageMeta({
layout: dynamicLayout.value // Evaluated at compile time, not runtime!
})
</script>
Imported Variables
<script setup>
import { pageConfig } from './config'
// ❌ Wrong - imported at runtime
definePageMeta({
title: pageConfig.title // Won't work as expected
})
</script>
Patterns for Dynamic Page Meta
Dynamic Title with useHead
<script setup>
// Static meta
definePageMeta({
layout: 'default'
})
// Dynamic title
const route = useRoute()
const { data } = await useFetch(`/api/products/${route.params.id}`)
useHead({
title: computed(() => data.value?.name || 'Product')
})
</script>
Conditional Layout with setPageLayout
<script setup>
definePageMeta({
layout: false // Disable default layout
})
// Set layout dynamically
const user = useUser()
onMounted(() => {
if (user.value?.isAdmin) {
setPageLayout('admin')
} else {
setPageLayout('default')
}
})
</script>
Dynamic Middleware
<script setup>
// Can't dynamically add middleware in definePageMeta
// Instead, create a middleware that handles the logic
definePageMeta({
middleware: 'dynamic-auth'
})
</script>
// middleware/dynamic-auth.ts
export default defineNuxtRouteMiddleware(async (to) => {
// Dynamic logic here
const config = await $fetch('/api/auth-config')
if (config.requiresAuth && !isAuthenticated()) {
return navigateTo('/login')
}
})
Route Params for Dynamic Content
<script setup>
definePageMeta({
// ✅ Validate uses route params
validate: async (route) => {
// This async IS allowed in validate
const exists = await $fetch(`/api/products/${route.params.id}/exists`)
return exists
}
})
// Fetch data based on route
const route = useRoute()
const { data } = await useFetch(`/api/products/${route.params.id}`)
</script>
Build-Time vs Runtime
<script setup>
// definePageMeta is extracted at BUILD TIME
// This means:
// 1. Values are determined when you run 'nuxt build'
// 2. No access to runtime context
// 3. Must be statically analyzable
definePageMeta({
layout: 'default' // This is baked into the build
})
// Everything else runs at RUNTIME
// This means:
// 1. Has access to route, user, etc.
// 2. Can make API calls
// 3. Can be dynamic
const { data } = await useFetch('/api/data') // Runtime
</script>
Quick Checklist
- No
awaitinside definePageMeta - No function calls that fetch data
- No reactive values (.value)
- Use useHead for dynamic titles
- Use setPageLayout for dynamic layouts
- Put dynamic logic in middleware
- validate function CAN be async (exception)