Vue 53: SSR & the Nuxt Mental Model
easy⏱ 5 mincoursevue
Two renders, one app
In SSR, the server calls renderToString(app) and produces an HTML string sent to the browser for a fast first paint; the client then runs that same app code, and Vue 'hydrates' it: attaching listeners and reactive bindings to the DOM that already exists, instead of discarding it and re-rendering from scratch.
// server entry (Node)
import { createSSRApp } from 'vue';
import { renderToString } from 'vue/server-renderer';
const app = createSSRApp(RootComponent);
const html = await renderToString(app);
// respond with an HTML page containing 'html'
// client entry (browser)
import { createSSRApp } from 'vue';
createSSRApp(RootComponent).mount('#app'); // hydrates, doesn't re-render from scratch
Nuxt automates this
Nuxt builds on top of Vue to give you file-based routing, auto-imports, and a rendering mode you choose per app or per route - universal SSR by default, static generation (SSG), or pure client rendering - without hand-writing the server/client entry split yourself.
Simulating the gap in this sandbox
There's no real server here, so the demo below fakes the network+parse delay with a timeout, flipping from a 'static-looking' badge to 'hydrated' once the timer fires - that timing gap is exactly what a real user perceives between first paint and the page becoming clickable.
Hydration mismatches
If the server-rendered HTML and the client's first render don't match exactly (for example, using Math.random() or Date.now() directly in a template), Vue logs a hydration mismatch warning and may re-render - a very common real-world Nuxt bug.