Fix: setPageLayout Causes Hydration Errors in Nuxt

Error message:
[warn] [nuxt] `setPageLayout` should not be called to change the layout on the server within a component as this will cause hydration errors.
ssr 2025-01-25

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

  1. Calling setPageLayout during SSR (server-side rendering)
  2. Calling setPageLayout during the hydration phase on the client
  3. Calling setPageLayout in 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

  1. Server: Renders page with layout A
  2. Client hydration: Calls setPageLayout('B') → layout changes
  3. Mismatch: Server HTML has layout A, client expects B
  4. 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 setPageLayout in <script setup> unconditionally
  • Use definePageMeta for static layouts
  • Use computed in definePageMeta for dynamic layouts
  • Only use setPageLayout in onMounted or event handlers
  • Consider middleware for complex layout logic