Nuxt 3 SSR and Hydration Setup for SEO and First Load Performance
Render initial HTML on the server with Nuxt 3, prefetch data with useFetch and useAsyncData, and isolate browser-only code to avoid hydration mismatches and duplicate requests.
26 May 2026, 15:56 UTC

Pages that render empty on first load or fetch the same data twice after hydration hurt SEO and Time to Interactive. In Nuxt 3 the useful takeaway is to render the initial HTML on the server with Nitro, ship the data payload with that HTML, and hydrate only once on the client.
Desired outcome
Server-rendered HTML is delivered for crawlers and users with JavaScript disabled. Client hydration attaches interactivity without duplicate network requests. Browser-only APIs are isolated so the server build does not crash.
Prerequisites
- A Nuxt 3 project with nuxt.config.ts in the project root.
- Node.js runtime available in a terminal at the project root. No elevated permissions are required for local development.
- Understanding that SSR means Server-Side Rendering, where the initial render runs in Nitro, and hydration is the client process that takes over the server HTML.
Configure SSR mode and data fetching
Set rendering mode in nuxt.config.ts
Nuxt 3 defaults to universal rendering. Explicitly declare the mode to avoid accidental SPA fallback.
export default defineNuxtConfig({ ssr: true})Run npm run dev in the project root to start the dev server. Risk: setting ssr to false switches the app to single-page application mode and removes server HTML for all routes.
Prefetch data with useFetch and useAsyncData
useFetch and useAsyncData run on the server during the initial request and serialize the result into the HTML payload. The client then reuses that payload instead of issuing a second request.
const { data, pending } = await useFetch('/api/articles', { key: 'articles-list'})Place this composable in a page or component setup script, not inside onMounted. Running it in setup ensures server execution. Risk: using a client-only fetch inside onMounted causes a duplicate request after hydration.
Isolate browser-only code
Accessing window or document in setup crashes the server because those globals do not exist in Nitro. Guard access or defer to the client.
import { onMounted, ref } from 'vue'const isReady = ref(false)onMounted(() => { isReady.value = true})For components that depend on browser APIs, wrap them with ClientOnly.
<ClientOnly> <MapWidget /></ClientOnly>ClientOnly prevents server execution and renders a fallback on the server. Risk: overusing ClientOnly increases client bundle size and delays Time to Interactive.
Expected checks
- Disable JavaScript in the browser and reload the page. Content should remain visible from the initial HTML response.
- Open the network tab and reload with JavaScript enabled. Verify that data used for the initial render is present in the HTML payload and not triggered as a separate XHR on load.
- Check the browser console for hydration node mismatch warnings. Absence of warnings indicates DOM consistency between server HTML and client state.
Limitations and recovery options
Hydration mismatches occur when server-rendered markup differs from the first client render, for example from random values or time-dependent output. Recovery is to make the output deterministic or move the differing part into ClientOnly.
If a third-party library cannot run on the server, do not try to polyfill window on the server. Isolate the library with ClientOnly and accept that the initial HTML will be a placeholder.
Rollback for a problematic page is to set ssr to false for that route via route rules or to wrap the entire page in ClientOnly. This restores SPA behavior but loses server HTML for SEO.
Verification steps above require manual browser checks. Current behavior of Nitro presets and payload serialization should be confirmed in the target deployment environment.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.