Hydration

Nuxt's default is universal rendering (SSR).
This means components execute twice:

  1. Server-side: Generate HTML
  2. Client-side: Add JavaScript functionality to HTML (hydration)

Understanding this "runs twice" characteristic is crucial for Nuxt application development.

What is Hydration?

Hydration is the process of attaching Vue's reactivity system and event listeners on the client side to the static HTML generated on the server side.

The process follows these steps:

Step 1 (Server)

  • Vue component executes
  • HTML is generated: <p>Count: 0</p><button>Increment</button>
  • This HTML is sent to the browser

Step 2 (Client)

  • Browser displays the HTML (at this point it's just HTML, buttons don't work)
  • JavaScript loads
  • Vue executes again
  • Event listeners are "hydrated (attached)" to the existing HTML
  • Buttons become clickable

Experience a Hydration Error

Look at the preview on the right. Open the browser's developer tools (F12 key) and check the Console tab.

You should see a warning:

Hydration completed but contains mismatches.

This is a "hydration mismatch" error. Why is this error occurring?

Let's look at the code in app.vue:

<script setup>
const timestamp = Date.now() // ❌ This is the problem!

console.log('timestamp:', timestamp)
</script>

<template>
  <div>
    <p>Timestamp: {{ timestamp }}</p>
  </div>
</template>

What's the Problem?

  1. Server-side (check the Terminal tab below):
    • Date.now() executes and generates a timestamp at that moment
    • Example: timestamp: 1729641234567
  2. Client-side (browser's Console tab):
    • Date.now() executes again a few milliseconds later
    • Example: timestamp: 1729641234892

Since the execution time differs between server and client, they get different values, causing a hydration mismatch.

Why is This Serious?

Hydration errors are not just warnings:

  • ⚠️ Performance degradation (entire component re-renders)
  • ⚠️ Event listeners like buttons may not work
  • ⚠️ Unexpected display issues

Let's learn how to fix this in the next challenge.

Hydration Mismatch

When the HTML generated on the server differs from the HTML generated on the client, a "hydration mismatch" error occurs.

Hydration mismatch is not just a warning. It causes serious problems:

  • Performance: Increased time to become interactive
  • User Experience: Content flickering
  • Functionality: Event listeners don't work correctly
  • SEO: Search engines and users may see different content

Common Causes of Mismatch

1. Using Browser-Only APIs

Bad Example:

<script setup>
// window doesn't exist on the server!
const width = window.innerWidth
</script>

<template>
  <p>Width: {{ width }}</p>
</template>

Good Example:

<script setup>
const width = ref(0)

onMounted(() => {
  // onMounted only executes on the client
  width.value = window.innerWidth
})
</script>

<template>
  <p>Width: {{ width }}</p>
</template>

2. Time-Based Content

Bad Example:

<script setup>
// Execution time differs between server and client
const now = new Date().toISOString()
</script>

<template>
  <p>{{ now }}</p>
</template>

Good Example:

<script setup>
// useState transfers server value to client
const now = useState('timestamp', () => new Date().toISOString())
</script>

<template>
  <p>{{ now }}</p>
</template>

3. Using Random Values

Bad Example:

<script setup>
// Different values on server and client
const id = Math.random()
</script>

Good Example:

<script setup>
// useState maintains consistency
const id = useState('random-id', () => Math.random())
</script>

SSR Lifecycle

Code inside <script setup> executes both on server and client,
but execution timing differs based on lifecycle hooks.

What Runs on SSR (Server-Side)

  • Top-level code in <script setup> (ref(), reactive(), etc.)
  • useAsyncData() / useFetch()
  • Server-specific hooks like onServerPrefetch()

What Runs Only on CSR (Client-Side)

  • onBeforeMount() / onMounted()
  • onBeforeUpdate() / onUpdated()
  • onBeforeUnmount() / onUnmounted()
// ⭕ Runs on both server and client
const count = ref(0)

// ⭕ Runs only on server
onServerPrefetch(async () => {
  // Server-side data fetching
})

// ❌ Doesn't run on server (client only)
onMounted(() => {
  console.log('mounted')
})

This means browser-specific APIs (window, document, localStorage, etc.) need to be guaranteed to execute only on the client side.

Common methods:

  • Use inside onMounted()
  • Wrap with <ClientOnly> component
  • Guard with import.meta.client
  • Use .client.vue files

ClientOnly Component

For components that should only render on the client side, use the <ClientOnly> component.

<template>
  <div>
    <p>This displays on both server and client</p>

    <ClientOnly>
      <p>This only displays on client</p>
      <!-- window and document can be safely used here -->
    </ClientOnly>
  </div>
</template>

Content inside <ClientOnly>:

  • Skipped during server-side rendering
  • Only hydrated on client side
  • Prevents hydration mismatch

Challenge: Fix the Hydration Error

Currently, a hydration error is occurring in app.vue.
Let's implement a safe way to display the timestamp to fix this!

Task

Edit the following two files to resolve the hydration error:

1. Complete components/BrowserOnly.vue

  • Fill in the TODO comments to safely retrieve and display timestamp using onMounted
  • Create a ref to hold the timestamp (initial value: 0)
  • Set Date.now() inside onMounted

2. Fix app.vue

  • Remove the problematic timestamp-related code (both script and template parts)
  • Add the <BrowserOnly /> component at the TODO comment location

Hints

  • onMounted only executes on the client side
  • Setting initial value to 0 makes server and client initial state match
  • Executing Date.now() inside onMounted means it won't run on the server

When Complete...

✅ The hydration warning will disappear from the browser's developer tools Console
✅ The timestamp will display safely along with a green success message

If you get stuck, click the button below or the button in the top-right of the editor to see the solution.

Summary

Key points about hydration:

  1. Components execute twice (server + client)
  2. Lifecycle hooks are client-only (onMounted, etc.)
  3. Use browser APIs inside onMounted
  4. Keep same values on server and client (use useState)
  5. Separate client-only content with <ClientOnly>
Rendering Modes
Nuxt supports various rendering modes. Specifically, these include Universal Rendering, Client-Side Rendering, and Hybrid Rendering.
State Management
In Vue.js, state management refers to managing reactive state within an application.Vue.js Official Documentation: State Management
Files
Editor
Initializing WebContainer
Mounting files
Installing dependencies
Starting Nuxt server
Waiting for Nuxt to ready
Terminal