Client-side rendering and SSR in Nuxt: choose by product need
How SSR, client-side rendering, prerendering and cached routes fit real Nuxt products, from public acquisition pages and catalogues to authenticated dashboards and browser-heavy tools.
Rendering is a product decision made route by route
Server-side rendering produces HTML for a request before the browser hydrates it. Prerendering produces HTML during a build. Client-side rendering sends an application shell and waits for JavaScript in the browser to construct the route. Cached SSR or incremental regeneration sits between those positions.
Each choice moves cost, freshness and responsibility. SSR spends server work to return current HTML. Prerendering moves that work to deployment. Client rendering moves it to the visitor’s device. None is automatically modern or obsolete; each serves a different product requirement.
Nuxt supports hybrid rendering, so a product does not need one ideological mode everywhere. Decide what a first visit must contain, how current it must be, whether it is user-specific and which runtime APIs it needs.
export default defineNuxtConfig({
routeRules: {
"/": { prerender: true },
"/articles/**": { prerender: true },
"/products/**": { isr: 900 },
"/account/**": { ssr: true },
"/admin/**": { ssr: false },
},
})Public acquisition pages benefit from ready HTML
A marketing homepage, service page, article or public product description needs to work on a first visit from search, a shared link or a slow device. Returning the title, copy, navigation and metadata in HTML gives the browser useful content before the application hydrates and gives crawlers an unambiguous document.
If that content changes only when the product is deployed, prerendering is normally a stronger fit than request-time SSR. It provides the same ready HTML without running Vue on a server for every visit. A publishing event then needs to trigger a new build or another regeneration mechanism.
Do not use SSR only as an SEO switch. The product still needs useful headings, metadata, internal links and content. Rendering makes those signals available early; it does not make vague or duplicated content valuable.
- Company and campaign landing pages
- Documentation and technical articles
- Public pricing and service descriptions
- Portfolios and case studies
- Public product pages whose content changes with a deployment
Catalogues often need cached server rendering
An ecommerce catalogue or marketplace may contain public pages that should be indexable but change more often than the application is deployed. Rendering every request can be wasteful, while rebuilding thousands of routes after each stock or description change may be slow.
Nuxt route rules can cache rendered output and regenerate it after a time window. On platforms that support it, ISR stores the response at the CDN; SWR can serve a cached response while refreshing it in the background. The correct window follows the product promise rather than an arbitrary round number.
Price, availability and permission-sensitive actions may still need a live request even when descriptive page content is cached. Separate information that can be slightly stale from information that must be checked before a purchase or decision.
export default defineNuxtConfig({
routeRules: {
"/collections/**": { isr: 3600 },
"/products/**": { isr: 300 },
"/checkout/**": { ssr: true },
},
})
// A cached product page can describe the item.
// Checkout must still verify current price and availability.Authenticated SaaS products usually mix server and client work
A dashboard is not automatically a single-page application merely because it is behind a login. SSR can establish the session, enforce a redirect and return useful account context on a direct visit. After hydration, client-side navigation and data refresh can make repeated interaction feel immediate.
Equally, not every dashboard panel needs server-rendered data. A chart opened after the user selects a date range, an editor with extensive local state or a modal loaded on demand can remain client-owned. Rendering the account shell on the server does not require every interaction to happen there.
Authentication must be enforced by server endpoints regardless of rendering mode. Hiding a link or redirecting in client middleware improves the interface but does not protect private data. The API or server route must authenticate and authorise every protected operation.
const { data: account, status, refresh } = await useFetch(
"/api/account",
{ key: "current-account" },
)
// A direct request can include account HTML in the response.
// The browser can refresh the same state after a mutation.
async function updateProfile(input: ProfileInput) {
await $fetch("/api/profile", { method: "PUT", body: input })
await refresh()
}Client-only rendering fits browser-owned tools
Some routes depend so heavily on browser APIs and local interaction that server rendering adds little. A visual editor, internal administration tool, offline-capable workspace or hardware-connected interface may need canvas, IndexedDB, drag-and-drop, media devices or another browser-only capability before it becomes useful.
Using ssr: false for that route avoids pretending the server can render the meaningful state. The trade-off is an emptier initial document, greater dependence on JavaScript and more work on the user’s device. Provide a useful loading shell and keep the client bundle under control.
Client-only rendering should follow the product context. An authenticated internal tool with no search requirement has different needs from a public configurator intended to attract search traffic. A browser API in one component is not a reason to make the entire application client-only; ClientOnly or a client-specific component can isolate that boundary.
- Visual editors and diagramming tools
- Internal administration interfaces
- Offline-first workspaces backed by browser storage
- Tools using cameras, microphones or connected devices
- Highly interactive routes whose initial state exists only in the browser
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.