What Causes This Error?
This warning appears when you call setPageLayout() in a context where it will cause a mismatch between server-rendered HTML and client-side rendering (hydration mismatch).
When This Happens
- Calling
setPageLayoutduring SSR (server-side rendering) - Calling
setPageLayoutduring the hydration phase on the client - Calling
setPageLayoutin a component’s setup (before mount)
The Problem
<script setup>
// ❌ Wrong - called during SSR and hydration
const user = useUser()
if (user.value?.isAdmin) {
setPageLayout('admin') // Causes hydration error!
}
</script>
The Fix
Option 1: Use definePageMeta (Preferred)
<script setup>
// ✅ Correct - static layout assignment
definePageMeta({
layout: 'admin'
})
</script>
Option 2: Computed Layout with definePageMeta
<script setup>
// ✅ Correct - dynamic but evaluated at route level
definePageMeta({
layout: computed(() => {
const user = useUser()
return user.value?.isAdmin ? 'admin' : 'default'
})
})
</script>
Option 3: Use onMounted for Client-Side Changes
<script setup>
// ✅ Correct - only changes layout on client after mount
onMounted(() => {
const user = useUser()
if (user.value?.isAdmin) {
setPageLayout('admin')
}
})
</script>
Option 4: Middleware for Layout Logic
// middleware/admin-layout.ts
export default defineNuxtRouteMiddleware((to) => {
const user = useUser()
// Set layout via page meta instead
if (user.value?.isAdmin) {
to.meta.layout = 'admin'
}
})
<script setup>
definePageMeta({
middleware: 'admin-layout'
})
</script>
Understanding the Hydration Issue
What Happens
- Server: Renders page with layout A
- Client hydration: Calls
setPageLayout('B')→ layout changes - Mismatch: Server HTML has layout A, client expects B
- Error: Vue detects the mismatch and warns
The Solution Pattern
Layout must be determined before rendering, not during component setup:
<script setup>
// ✅ Layout determined at route definition time
definePageMeta({
layout: 'admin'
})
// ❌ Layout changed during component execution
setPageLayout('admin')
</script>
Common Patterns
Role-Based Layouts
<!-- pages/dashboard.vue -->
<script setup>
definePageMeta({
layout: computed(() => {
// This runs at route evaluation, not component render
const auth = useAuth()
return auth.value?.role === 'admin' ? 'admin' : 'user'
})
})
</script>
Feature Flag Layouts
<script setup>
definePageMeta({
layout: computed(() => {
const config = useRuntimeConfig()
return config.public.newDesign ? 'modern' : 'classic'
})
})
</script>
Conditional Layout in App.vue
<!-- app.vue -->
<script setup>
const route = useRoute()
const layout = computed(() => route.meta.layout || 'default')
</script>
<template>
<NuxtLayout :name="layout">
<NuxtPage />
</NuxtLayout>
</template>
When setPageLayout IS Safe
<script setup>
// ✅ Safe - user interaction triggers layout change
const toggleLayout = () => {
setPageLayout(currentLayout.value === 'wide' ? 'narrow' : 'wide')
}
</script>
<template>
<button @click="toggleLayout">Toggle Layout</button>
</template>
<script setup>
// ✅ Safe - called after hydration in onMounted
onMounted(() => {
if (someClientOnlyCondition) {
setPageLayout('special')
}
})
</script>
Migration from setPageLayout
If you’re using setPageLayout during setup:
<!-- Before -->
<script setup>
if (condition) {
setPageLayout('custom')
}
</script>
<!-- After -->
<script setup>
definePageMeta({
layout: computed(() => condition ? 'custom' : 'default')
})
</script>
Quick Checklist
- Don’t call
setPageLayoutin<script setup>unconditionally - Use
definePageMetafor static layouts - Use
computedindefinePageMetafor dynamic layouts - Only use
setPageLayoutinonMountedor event handlers - Consider middleware for complex layout logic