Nuxt rendering: choosing SSR, static generation and client-side work
How Nuxt rendering choices affect HTML, hydration, data freshness, browser APIs and deployment—and why one application can use more than one strategy.
Rendering is a route-level product decision
Server-side rendering produces HTML for a request before the browser hydrates it. Static generation produces HTML during a build. Client-side rendering waits for JavaScript in the browser to construct the view. Each moves work and freshness to a different point in the system.
Public content often benefits from ready HTML and predictable metadata. An authenticated dashboard may rely more heavily on client-side state. Nuxt can combine strategies, so the application does not need one ideological rendering mode for every route.
export default defineNuxtConfig({
routeRules: {
"/": { prerender: true },
"/insights/**": { prerender: true },
"/account/**": { ssr: true },
"/api/**": { cors: true },
},
})Hydration requires the first render to agree
Hydration attaches Vue behaviour to server-rendered HTML. If the browser produces different initial markup, Vue must reconcile a mismatch. Current time, random values, browser-only storage and viewport-dependent branches are common causes.
Move browser-only reads into onMounted or a client-only component. Pass stable server data through the Nuxt payload rather than fetching it independently on both sides with different timing.
const preferredTheme = ref("system")
onMounted(() => {
preferredTheme.value = localStorage.getItem("theme") ?? "system"
})
// The server and first client render both begin with "system".useFetch connects server rendering and hydration
Nuxt useFetch wraps asynchronous data in SSR-aware state. Server rendering can wait for the request and serialize the result into the payload so hydration does not need to repeat the same fetch.
Use its data, status and error refs explicitly. Lazy or non-awaited fetching can keep navigation responsive, but the page then owns a loading state. Choose blocking or deferred navigation according to what must be present for the route to be useful.
const route = useRoute()
const { data: article, status, error } = await useFetch(
() => `/api/articles/${route.params.slug}`,
{ key: `article-${route.params.slug}` },
)
if (error.value?.statusCode === 404) {
throw createError({ statusCode: 404, statusMessage: "Article not found" })
}Static output changes the meaning of freshness
A generated page contains data available during the build. Publishing a database change does not update that HTML until another deployment or regeneration strategy runs.
This is excellent for versioned articles and marketing pages. It is a poor default for account balances or permission-sensitive data. Document which event makes generated content fresh again and avoid embedding secrets or user-specific responses at build time.
Deployment runtime completes the architecture
A Nuxt build can include server-rendered routes and Nitro handlers; a fully generated build contains static output. The target platform determines function duration, filesystem behaviour, regions and environment configuration.
Test the actual deployment shape. Local development can conceal cold starts, missing variables, cross-region database latency and assumptions about a writable filesystem. Preview deployments are useful only when their data and authentication configuration are intentionally separated from production.
Choose from the user-visible requirement backwards
Ask whether crawlers or first-time visitors need complete HTML, how fresh the result must be, whether it contains user-specific data and what should happen without JavaScript. Those answers determine rendering more reliably than framework fashion.
Then verify the generated HTML, hydration, navigation, cache behaviour and deployed runtime. A route is not correctly rendered merely because it looks right after a warm client-side navigation.