In Nuxt 4, what HTML does a visitor first receive under the default ssr: true, under ssr: false, and from nuxt generate?
answer
- universal rendering is the default
- ssr: false sends an empty shell
- a loading template fills the wait
- generate writes into .output/public
- no server after generate
basics
~20 sWith Nuxt 4's default ssr: true, each request gets server-rendered HTML plus a payload for hydration. ssr: false sends a shell with an empty #__nuxt root for the browser to fill. nuxt generate prerenders pages into static HTML files at build time.
solid answer
~40 sNuxt 4 defaults to universal rendering (`ssr: true`): after `nuxt build`, Nitro renders each requested page on the server, so the first response already holds the content plus a serialized payload of fetched data and state, and the browser hydrates it. With `ssr: false` in `nuxt.config.ts` the response is a shell whose `<div id="__nuxt">` is empty, optionally with `app/spa-loading-template.html` shown until the app renders; all rendering and data fetching then happen in the browser. `nuxt generate`, the same as `nuxt build --prerender`, renders pages at build time: the crawler starts from `/`, follows links, and writes HTML and `_payload.json` files into `.output/public/`, plus `200.html` and `404.html` fallbacks. That output has no server, so server routes are gone.
code
ts · 12 lines// nuxt.config.ts
export default defineNuxtConfig({
// default is true: universal rendering
ssr: true,
nitro: {
prerender: {
// for nuxt generate: pages no link reaches
routes: ['/sitemap.xml'],
ignore: ['/preview'],
},
},
})go deeper
Recall the three outcomes: SSR by default, an empty shell with ssr: false, and build-time HTML files from nuxt generate.
Explain how the payload avoids refetching on hydration, how the generate crawler finds pages, and why dynamic pages need listing.
Spell out the operational consequences: a server to run for SSR, no server routes after generate, and stale data until the next build.
Set the app-wide default by audience and data freshness, then justify the per-route exceptions a team will add over time.
## Who renders the first HTML Every Nuxt page is a Vue component tree, and **rendering** means turning it into HTML. Nuxt 4 lets you choose where that happens for the first response, app-wide, with one config key and one command: | Setup | Where the first HTML is produced | Body of the first response | Running server needed | |---|---|---|---| | default `ssr: true`, `nuxt build` | on the server, per request | full page markup plus a serialized payload | yes | | `ssr: false`, `nuxt build` | in the browser | shell with an empty `<div id="__nuxt">` | yes, to serve the shell and server routes | | `nuxt generate` with `ssr: true` | at build time | prebuilt HTML file plus `_payload.json` | no | | `nuxt generate` with `ssr: false` | in the browser | static shell such as `index.html` | no | ## Universal rendering: the default - `ssr` defaults to `true`, which Nuxt calls **universal rendering**. - `nuxt build` produces `.output/`, including a server entry, `.output/server/index.mjs`, that you start with Node. - For each request the Nitro server runs the Vue app: route middleware, the page component and its data fetching through `useFetch` or `useAsyncData`. It returns complete HTML. - The HTML carries a **payload**, the fetched data and `useState` values, so the browser can **hydrate** the markup without fetching again. After that first load, navigation happens client-side. - The cost is a server that must run, and component code that must be safe to execute on both sides. ## ssr: false: client-side rendering - `ssr: false` in `nuxt.config.ts` turns server rendering off for the whole app. - The response is a **shell**: head tags, links to the scripts and styles, the public runtime config, and an empty `<div id="__nuxt">`. - If `app/spa-loading-template.html` exists, its markup is shown until the app mounts. Nuxt 4 places it beside `#__nuxt` rather than inside it. - All data fetching runs in the browser, crawlers see an empty body, and payload extraction is switched off. In return, browser-only APIs are usable anywhere and there is nothing to hydrate. ## nuxt generate: prerendering at build time 1. `nuxt generate` is the same as `nuxt build --prerender`: it builds for static output with Nitro's link crawler switched on. 2. The crawler loads `/`, the pages without dynamic segments, and any routes listed in `nitro.prerender.routes`, then follows the `<a href>` links it finds. 3. Each page's HTML and its `_payload.json` are written into `.output/public/`, together with `200.html` and `404.html` fallbacks for the static host. 4. You deploy `.output/public/` to any static file host. The consequences are what interviewers probe: - A page that no link reaches and no list names is **not generated**. - Data is frozen at build time; client-side navigation reuses the build-time `_payload.json` until the next build. - There is **no server** in the output, so endpoints under `server/api/` do not exist, and caching route rules such as `swr` or `isr` do not apply. - Nuxt 4 removed the top-level `generate` config key; routes to add or skip now go in `nitro.prerender.routes` and `nitro.prerender.ignore`. ## Choosing, and mixing per route These are app-wide defaults. Most real apps keep the default and mix modes per URL with `routeRules`, prerendering some pages and making others client-only, while still building with `nuxt build`. A quick guide: - same content for everyone and must be indexed: prerender; - per-request data, such as live stock or prices: universal SSR; - behind a login and browser-heavy: client-side rendering. ## Common confusions - **`ssr: false` is not serverless.** After `nuxt build` a Nitro server still ships, serving the shell and any server routes. - **`nuxt generate` is not SPA mode.** With `ssr: true` each generated file holds the page's full markup. - **Prerendered pages still hydrate.** In the browser they behave exactly like server-rendered ones. ## Seeing the difference yourself The fastest way to tell the modes apart is to look at the raw response rather than the rendered page: - **View the page source** or fetch the URL with a command-line HTTP client. Universal and prerendered pages show their headings and text inside `#__nuxt`; an `ssr: false` page shows an empty root. - **Disable JavaScript** in the browser. Server-rendered and prerendered content stays readable; a client-rendered page stays blank or shows only the loading template. - **Inspect `.output/`** after the build. `nuxt build` leaves a `server/` folder to run; `nuxt generate` leaves only `public/`, with one HTML file per generated route. These checks are what an interviewer expects when asking how you would prove which mode a page actually uses.
- The team runs nuxt generate, and some product pages are missing from .output/public. Why?The generator only renders what it can find: `/`, the pages without dynamic segments, the routes in `nitro.prerender.routes`, and every internal link reached from those. A dynamic page such as `products/[id].vue` that no crawled page links to is never visited. List those URLs in `nitro.prerender.routes`, add them in the `prerender:routes` hook, or link them from a crawled page.
- What does a static host need to serve for a generated site's unknown URLs?`nuxt generate` writes two fallbacks into `.output/public/`. `200.html` loads the app so the client router can render the URL, and `404.html` does the same while the host keeps a 404 status. The host must be configured to serve one of them for paths that have no file of their own.
saying these in an interview costs you the question
- Nuxt 4 renders pages only in the browser unless SSR is enabled
- With ssr: false, nuxt build outputs static files only
- nuxt generate produces a client-only SPA with empty pages
- Prerendered pages skip hydration because they are static
- Every dynamic page is generated automatically by nuxt generate
- Server API routes keep working after nuxt generate