Best practices to avoid hydration mismatches
To avoid hydration mismatches: use SSR-friendly composables like useFetch, useAsyncData, and useState; wrap client-only code with ClientOnly component for browser-specific content; ensure server and client use the same data sources; avoid side effects in setup by moving browser-dependent code to onMounted hook.
Hydration mismatch performance impact
Hydration errors force Vue to re-render the entire component tree, which increases the time for a Nuxt app to become interactive. Users may see content flashing or unexpected layout shifts.
Hydration mismatch functionality issues
Hydration mismatches can cause broken interactivity where event listeners may not attach properly, leaving buttons and forms non-functional. They can also cause state inconsistencies where application state becomes out of sync between what the user sees and what the application thinks is rendered. Additionally, search engines may index different content than what users actually see, causing SEO problems.
Browser-only APIs in server context causes hydration mismatch
Using browser-specific APIs like localStorage during server-side rendering causes hydration mismatches because these APIs do not exist on the server. The solution is to use useCookie() which works on both server and client.
Inconsistent data between server and client causes hydration mismatch
Different data being rendered on the server versus the client causes hydration mismatches. For example, using Math.random() directly will produce different values on server and client. The solution is to use useState() composable which is SSR-friendly.
Conditional rendering based on client state causes hydration mismatch
Using client-only conditions during SSR like checking window.innerWidth causes hydration mismatches because these conditions cannot be evaluated on the server. The solution is to use CSS media queries or handle the logic client-side only.
Third-party libraries with side effects cause hydration mismatch
Libraries that modify the DOM or have browser dependencies (such as tag managers) can cause hydration mismatches. The solution is to initialize these libraries after hydration has completed using onMounted() hook, and conditionally import them only on the client using import.meta.client.
Dynamic content based on time causes hydration mismatch
Content that changes based on the current time causes hydration mismatches because the server and client calculate time differently. Solutions include using the NuxtTime component or handling the logic client-side using ClientOnly component with onMounted() hook.
Hybrid rendering with Route Rules
Hybrid rendering allows different caching rules per route using Route Rules. It decides how the server should respond to a new request on a given URL, supporting mixed rendering strategies where some pages are generated at build time while others are client-side rendered.
Route Rules options
Route Rules in Nuxt support the following options: prerender (boolean, generates page at build time), swr (number in seconds for stale-while-revalidate caching), isr (number in seconds for incremental static regeneration), and ssr (boolean, set to false to disable server-side rendering for a route).
Example: Hash mode SPA configuration
export default defineNuxtConfig({
ssr: false,
router: {
options: {
hashMode: true,
},
},
})
Hash mode disables SSR
When hash mode is enabled via router.options.hashMode: true, the URL is never sent to the server and SSR is not supported. Hash mode uses a hash character (#) before the actual URL internally.
hydrate-never lazy hydration attribute
The hydrate-never attribute is particularly useful on content sites. The component's HTML is server-rendered but it is never hydrated, so its JavaScript is never executed, though prop changes will still trigger hydration.
Lazy hydration attributes
Three lazy hydration attributes control when components become interactive: hydrate-never keeps a component server-rendered without hydration, hydrate-on-visible hydrates only when scrolled into view, and hydrate-on-idle hydrates when the browser is idle.
Typical mostly-static site structure with routeRules
A typical mostly-static site uses routeRules to combine strategies: pure content routes are prerendered with noScripts for zero JavaScript, pages with interactive widgets are prerendered but keep scripts, and interactive routes use lazy hydration to limit upfront browser work. This ensures content routes ship plain HTML and CSS with speculatively prefetched document loads, islands handle server-rendered widgets everywhere, and few interactive routes keep scripts only where needed.
View transitions with full-page navigation
When view transitions are enabled with experimental.viewTransition: true, noScripts pages opt into cross-document view transitions so that full-page navigation animates rather than flashing.
Prerender with noScripts route rule example
Routes with no client-side interactivity should combine prerender and noScripts in routeRules. Example: routes /blog/** and /about are rendered to plain HTML at build time with no Nuxt entry scripts.
Server components for static content widgets
Use server components (files ending in .server.vue) for widgets that benefit from real logic but don't need interactivity, such as rendered markdown, syntax-highlighted code, or CMS content. They render on the server and their dependencies never reach the client bundle. Static islands work fine on noScripts routes since their HTML is embedded at render time.
Server components incompatible with noScripts
Fully interactive widgets inside islands (components marked with nuxt-client or interactive island slots) do not work on a noScripts route. Island teleports are relocated by a small inline script before hydration; with scripts disabled, that relocation cannot run and nothing hydrates. If a page needs even one interactive widget, keep scripts on that route and use lazy hydration instead.
Lazy hydration for routes with interactivity
For routes that need some interactivity, keep scripts enabled and control when components hydrate using lazy hydration. Use Lazy prefix with components and lazy hydration attributes like hydrate-never, hydrate-on-visible, and hydrate-on-idle to control when components are hydrated.
Mostly-static sites strategy overview
To ship near-zero JavaScript for content and marketing sites while keeping islands of interactivity, combine prerendering, the noScripts route rule, server components for widgets, and lazy hydration on routes that keep their scripts. This avoids paying the cost of hydration JavaScript on pages that are mostly static text and images, improving metrics like mobile PageSpeed scores.
noScripts route rule effect
The noScripts route rule omits the Nuxt entry scripts, import map, payload script, and JavaScript resource hints from rendered pages. CSS is still included. A page with noScripts has effectively zero JavaScript cost.
noScripts limitations and interactivity
A noScripts page does not hydrate. Nothing on it is interactive: no @click handlers, no NuxtLink prefetching (links still work as plain <a> tags), and no client-side navigation away from the page. Only use noScripts on routes where full interactivity is not needed.
Navigation to and from noScripts routes
Because a noScripts page has no client-side router, every navigation to or from one is a full-page load. Navigating from a scripted route to a noScripts route triggers a document load, so the target is served script-free by the server. Pages covered by route rules are dropped from the client bundle. Both scripted and script-free pages emit declarative speculation rules tags so supporting browsers prefetch and prerender the target before the user follows a link.
noScripts interactions with server components and islands
Routes with the noScripts route rule are always rendered with the buffered (non-streaming) renderer. Server components that only render static HTML work fine, but components hydrated with nuxt-client and interactive island slots rely on an inline script to relocate teleported content, so they will not become interactive on a noScripts route. When features.noScripts is set app-wide and component islands are active, Nuxt falls back to buffered rendering.
noScripts route rule configuration
You can disable scripts per-route using the noScripts route rule, which applies in all modes. This is configured in nuxt.config.ts under routeRules with a pattern as the key and { noScripts: true } as the value. For example, routeRules: { '/blog/**': { noScripts: true } } disables scripts for all routes matching the /blog/** pattern.
noScripts with speculation rules and page navigation
When noScripts is enabled, Nuxt emits a speculation rules tag on pages so supporting browsers prefetch and prerender targets before the user follows a link. The rules are scoped to page routes (safe to GET) rather than all same-origin links, so links to server routes like '/logout' are never speculatively fetched. Client-side navigation to a route covered by a noScripts rule triggers a full document load instead of client-side rendering.
noScripts feature modes and behavior
The noScripts feature turns off rendering of Nuxt scripts and JavaScript resource hints. Possible values are: false (default, scripts rendered normally), 'production' or true (scripts omitted in production only), and 'all' (scripts omitted in both development and production). When noScripts applies, the following are omitted from HTML: Nuxt entry script tags, the import map for the entry chunk, the inlined payload script, and JavaScript resource hints (preload and prefetch links for JS chunks). CSS remains unaffected.
Example route rules configuration
Route rules example with various configurations: `'/'` with `prerender: true` for homepage pre-rendered at build time; `/products` with `swr: true` for on-demand generation with background revalidation; `/products/**` with `swr: 3600` cached for 1 hour; `/blog` with `isr: 3600` for CDN cache 1 hour; `/blog/**` with `isr: true` for CDN cache until next deployment; `/admin/**` with `ssr: false` for client-side only; `/api/**` with `cors: true` for CORS headers; `/old-page` with `redirect: '/new-page'` for server-side redirects.
Code example: Universal rendering script execution
In universal rendering, a `ref` like `const counter = ref(0)` executes in both server and client environments. An event handler like `const handleClick = () => { counter.value++ }` executes only in client environment. When rendering a template with `{{ counter }}` and `@click="handleClick"`, the ref initializes on the server during rendering, then re-initializes during hydration in the browser, and the event handler binds to the button.
Code example: Disable SSR in nuxt.config
To enable client-side only rendering, add to nuxt.config.ts: `export default defineNuxtConfig({ ssr: false, })`
Code example: Clear prerender routes
To prevent any routes from being prerendered except defaults when using `nuxt generate`, add to nuxt.config.ts: `export default defineNuxtConfig({ hooks: { 'prerender:routes' ({ routes }) { routes.clear() } } })`
Code example: Skip client fallback generation
To prevent `index.html`, `200.html`, and `404.html` from being generated when prerendering, add to nuxt.config.ts: `export default defineNuxtConfig({ ssr: false, nitro: { hooks: { 'prerender:generate' (route) { const routesToSkip = ['/index.html', '/200.html', '/404.html']; if (routesToSkip.includes(route.route)) { route.skip = true } } } } })`
Code example: Enable prerendered error pages
To prerender error pages, add to nuxt.config.ts: `export default defineNuxtConfig({ experimental: { prerenderErrorPages: true, } })`
Code example: Route rules in nuxt.config
Route rules configuration example in nuxt.config.ts with various cache strategies: `export default defineNuxtConfig({ routeRules: { '/': { prerender: true }, '/products': { swr: true }, '/products/**': { swr: 3600 }, '/blog': { isr: 3600 }, '/blog/**': { isr: true }, '/admin/**': { ssr: false }, '/api/**': { cors: true }, '/old-page': { redirect: '/new-page' } } })`
Hybrid rendering availability
Hybrid rendering is not available when using `nuxt generate`.
Three main rendering modes in Nuxt
Nuxt supports three main rendering modes: universal rendering (server-side rendering), client-side rendering, and hybrid rendering. Additionally, Nuxt offers Edge-Side Rendering (ESR) which allows rendering on CDN edge servers.
Universal rendering default in Nuxt
By default, Nuxt uses universal rendering to provide better user experience, performance, and to optimize search engine indexing. Universal rendering can be changed in one line of configuration in nuxt.config.ts using the ssr property.
Universal rendering process
In universal rendering, Nuxt runs JavaScript (Vue.js) code in a server environment and returns a fully rendered HTML page to the browser. Once the HTML document is downloaded, Vue.js takes control in the browser and the same JavaScript code runs again in the background to enable interactivity. This process is called hydration.
Hydration in universal rendering
Hydration is the process where Vue.js takes over a server-rendered HTML document in the browser by binding event listeners. When hydration is complete, the page can enjoy benefits such as dynamic interfaces and page transitions.
Benefits of universal rendering
Universal rendering provides quick page load times while preserving benefits of client-side rendering. Users immediately get access to page content because browsers display static HTML faster than JavaScript-generated content. Content is already present in the HTML document, allowing crawlers to index it without overhead.
Downsides of universal rendering
Universal rendering has development constraints because server and browser environments don't provide the same APIs, making it tricky to write code that runs on both sides seamlessly. There is also a cost to running a server to render pages on the fly, though this can be reduced by leveraging edge-side-rendering.
Universal rendering use cases
Universal rendering is especially appropriate for content-oriented websites including blogs, marketing websites, portfolios, e-commerce sites, and marketplaces.
What executes on server vs client in universal rendering
In universal rendering, initialization code (like creating refs) executes on both server and client. Event handlers and side effects execute only in the browser/client environment. Middlewares and pages run on the server and on the client during hydration. Plugins can be rendered on the server or client or both. Components can be forced to run on the client only. Composables and utilities are rendered based on the context of their usage.
Enable client-side only rendering
To enable client-side only rendering in Nuxt, set `ssr: false` in nuxt.config.ts. When using `ssr: false`, you should also place an HTML file in `~/spa-loading-template.html` with HTML for a loading screen that will be rendered until the app is hydrated.
Client-side rendering benefits
Client-side rendering provides development speed because there is no need to worry about server compatibility of code when working entirely on the client-side. It is cheaper to host since client-only applications can be hosted on any static server. It supports offline functionality since code runs entirely in the browser.
Client-side rendering downsides
Client-side rendering has performance impacts because users must wait for the browser to download, parse and run JavaScript files. Search Engine Optimization is negatively affected because indexing takes more time with client-side rendering and crawlers won't wait for the interface to be fully rendered on their first try.
Client-side rendering use cases
Client-side rendering is a good choice for heavily interactive web applications that don't need indexing or whose users visit frequently. It works well for SaaS, back-office applications, and online games where browser caching can skip the download phase on subsequent visits.
Deploying client-rendered app as static
When deploying a client-rendered app to static hosting with `nuxt generate` or `nuxt build --prerender`, by default Nuxt renders every page as a separate static HTML file. For a purely client-side rendered app, you might only need a single `index.html` file, plus `200.html` and `404.html` fallbacks.
200.html and 404.html fallback files
200.html should be served for unmatched paths when you want the client router to handle the URL. 404.html should be served when the host should keep a 404 status and still load the app. These files are written to `.output/public/` by `nuxt generate` and `nuxt build --prerender`. A plain `nuxt build` without prerender does not generate these files.
Prerender error pages
By default, 404.html is an empty shell, so error.vue and its layout and data only appear once the client app has booted. To prerender the error page, enable `experimental.prerenderErrorPages: true` in nuxt.config.ts. Pass an array of status codes between 400 and 599 to emit additional pages, such as `[404, 500]`.
import.meta.prerender usage
`import.meta.prerender` is a build-time flag that is only `true` while a page is being generated. It can be used to conditionally skip server-side data fetching during prerendering so request-specific data is fetched on the client instead. Request-specific markup should be wrapped in `<ClientOnly>`.
Skip client fallback generation with Nitro hook
When prerendering a client-rendered app, to prevent `index.html`, `200.html` and/or `404.html` files from being generated, use the `'prerender:generate'` hook from Nitro in nuxt.config.ts. Set `route.skip = true` for routes you want to skip.
Hybrid rendering definition
Hybrid rendering allows different caching rules per route using Route Rules and decides how the server should respond to a new request on a given URL. It enables different routes/pages of a Nuxt application to use different rendering modes - some pages can be generated at build time while others are client-side rendered.
Route rules in Nuxt
Route rules define rules for groups of Nuxt routes, allow changing rendering mode, and assign cache strategies based on route. Nuxt server automatically registers corresponding middleware and wraps routes with cache handlers using the Nitro caching layer.
Route rules configuration properties
Route rules support these properties: redirect (server-side redirects), ssr (disable/enable server-side rendering), cors (add CORS headers), headers (add specific headers), swr (cache with stale-while-revalidate), isr (incremental static regeneration for CDN), prerender (prerender at build time), noScripts (disable Nuxt scripts and JS resource hints), appMiddleware (define middleware for Vue app paths).
SWR vs ISR in route rules
SWR (stale-while-revalidate) caches the response on the server or reverse proxy for a configurable TTL. When TTL expires, the cached response is sent while the page regenerates in background. ISR (incremental static regeneration) behaves the same except it adds the response to CDN cache on platforms that support it (Netlify or Vercel). If `true` is used, ISR persists content until next deploy inside the CDN.
Payload extraction with SWR and ISR
Routes using `isr` or `swr` generate `_payload.json` files alongside HTML. Client-side navigation loads these cached payloads instead of re-fetching data.
Server bundle optimization with ssr: false
A route covered by `ssr: false` is only rendered in the browser, so Nuxt excludes its page component from the server bundle. This applies when rules covering every path that reaches the page can be resolved at build time, including dynamic routes. A page is kept in the server bundle when any path reaching it might still be rendered on the server, such as when a more specific rule re-enables SSR, the page has an alias outside the client-only region, or it's a parent rendering a server-rendered child.
Edge-Side Rendering definition
Edge-Side Rendering (ESR) is a feature that allows rendering of a Nuxt application closer to users via edge servers of a Content Delivery Network (CDN). ESR is more a deployment target than an actual rendering mode. When a page request is made, it's intercepted by the nearest edge server which generates the HTML and sends it back, minimizing physical distance data travels and reducing latency.