Server-side rendering makes a Vue app return real HTML on the first request — for SEO, faster first paint, and working link previews — instead of an empty shell the client fills in. Vue 3 supports SSR directly, but wiring it up by hand (server bundle, hydration, routing, data fetching) is a lot of undifferentiated work. For any real project, Nuxt is the pragmatic path, and the decisions worth your attention are its rendering modes and the universal-code gotchas.
Why Nuxt over hand-rolling
Hand-building Vue SSR means solving the same problems Nuxt already solved: a server render pipeline, client hydration, file-based routing, data fetching that works on both server and client, and build configuration for two targets. Nuxt packages all of it into a framework, so you write your app and get SSR, routing, and data fetching for free. Unless you have an unusual constraint, reinventing that stack is effort spent on plumbing rather than product.
Rendering modes
| Mode | Renders | Best for |
|---|---|---|
| SSR (universal) | On the server per request, hydrates on client | Dynamic, SEO-sensitive content |
| SSG (static) | At build time to static HTML | Content that rarely changes — marketing, docs, blogs |
| SPA (client-only) | In the browser, no server render | App-like, auth-gated dashboards where SEO is irrelevant |
Nuxt lets you choose per route, and often the right answer is a mix: statically generate the marketing and blog pages, server-render the dynamic public pages, and leave the authenticated app as a client-side SPA. Matching the mode to each route's needs is where the real optimisation is.
Universal-code gotchas
The defining challenge of SSR is that your code runs in two environments — Node on the server, then the browser. Code that assumes browser globals breaks on the server:
- No
windowordocumenton the server — guard browser-only code to run after mount (client-side only). - Data fetching must work on both sides — use the framework's data-fetching so it runs on the server for the initial render and the client for navigation.
- Watch for hydration mismatches — server and client must render the same output initially, or Vue warns and re-renders.
If the server renders one thing and the client expects another — because of a browser-only value, a timestamp, or randomness in the initial render — Vue throws a hydration mismatch and the benefit is lost. Keep the initial render deterministic and identical on both sides, and defer anything browser-specific until after hydration. Most SSR debugging is chasing these mismatches.
Vue 3 SSR is best reached through Nuxt, which packages the server pipeline, hydration, routing, and data fetching so you build product instead of plumbing. Choose the rendering mode per route — static for content, SSR for dynamic public pages, SPA for the app — and write universal code carefully, guarding browser globals and keeping the initial render identical on both sides to avoid hydration mismatches. That is where SSR is won or lost.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.