Rendering modes and deployment
Decide per route whether a page is built once, rendered on each request, kept in a cache for a while or left to the browser, then ship the result as a Node server or as a folder of files.
What you'll learn
When you finish, you can do these.- 1Choose a rendering mode for each kind of page
- 2Cache a page that changes sometimes and keep it fresh
- 3Ship a Node server and change its settings on the host
- 4Generate static files, including pages nothing links to
The Corner Shop, the small online shop this track builds, has four kinds of page. The home page changes only when you deploy, that is, put a new version online. The pricing page changes about once a month, in a CMS (content management system), with no new build. The journal, the shop's blog, has three posts, one shared only in the newsletter. The account area is different for every visitor. Treat all four the same, and something breaks: a server busy with pages that never change, stale prices, or one visitor's account shown to the next.
Rendering a page means turning its components into HTML. Nuxt lets you choose when that happens, page by page, then ship the site as a Node server or a folder of files. The Code tab holds the shop's files, with the rules for its pages in nuxt.config.ts, Nuxt's settings file. You need one idea from the lesson SEO, meta and error pages: search engines and link previews read the HTML a server sends, usually without running JavaScript.
Pick a rendering mode per route
One option in nuxt.config.ts, routeRules, gives each kind of page its own rendering mode. Without it, Nuxt renders every page on the server for each request, and the browser then hydrates it, attaching the code behind its buttons (SSR and hydration).
Each key in routeRules is a path or a pattern (/account/** matches everything under /account), checked before any page code runs, and its value says how to answer it.
- Request
- routeRules match
- prerendered file or cache hit
- else render on server
- or send empty shell
Here are the shop's rules for its home page and account area, plus two that are not rendering modes:
routeRules: {
'/': { prerender: true },
'/account/**': { ssr: false },
'/blog/**': { redirect: { to: '/journal/**', statusCode: 301 } },
'/downloads/**': { headers: { 'cache-control': 'public, max-age=86400' } },
},
prerender: true renders the home page once, during the build, so in production a file is served and no code runs. ssr: false does the opposite: the server sends a shell, an HTML page with an empty <div id="__nuxt"> for the app, and the browser renders the page, as in a single-page app. That fits the account area: nothing there is shared, and search engines have no business there.
redirect sends the journal's old /blog addresses to /journal for good (status 301), and headers adds response headers, here one that lets browsers keep /downloads files for a day.
Cache what changes sometimes
The pricing page needs a server, since its prices come from the CMS, but not a render per visitor. A cache rule renders it once and hands that copy to the visitors after it:
routeRules: {
'/pricing': { swr: 3600 },
'/offers': { isr: 3600 },
'/api/rates': { cache: { maxAge: 600 } },
},
swr: 3600 renders the page on the first request and keeps it for an hour, 3,600 seconds, with the header cache-control: s-maxage=3600, stale-while-revalidate. The page prints when it was rendered, and within the hour that time stays the same on every reload.
After the hour, the next visitor still gets the old copy at once while the server renders a fresh one in the background. Nobody waits, and a CMS edit shows up within the hour.
isr: 3600, here on an offers page, works like swr on Netlify and Vercel and also fills their CDN cache, copies kept on servers near visitors. A plain Node server ignores it, so use swr there.
cache: { maxAge: 600 } works like swr for ten minutes, on a server route, an API endpoint in server/api. Keep it for data every visitor shares, like the exchange rates at /api/rates. A cached /api/me, the signed-in visitor's details, would show one visitor's account to the next.
Build a server, or generate files
A finished site ships in one of two forms: a server that renders pages, or a folder of files. nuxt build makes the server, in one folder, .output/. node .output/server/index.mjs starts it on port 3000: the command a Node host runs. Nitro, the server engine inside Nuxt, shapes .output/ for each kind of host with a preset: node-server is the default, and vercel, netlify and cloudflare_module are picked on those platforms.
The site header shows the shop's name, Corner Shop, from runtimeConfig, the settings in nuxt.config.ts that a host can change (A Nuxt project tour). On the host, the environment variable NUXT_PUBLIC_SHOP_NAME, a value set outside the code, overrides it. The built server never reads the .env file used in development, and it reads variables when it starts: a change needs a restart, not a rebuild.
nuxt generate makes the folder of files: the site's pages in .output/public, for a static host, one that only serves files. With no server, routes under server/ are gone, and swr, isr, cache and redirect have nothing to run on. It also writes 200.html and 404.html, empty shells for a path with no file.
Pages with a fixed path, such as /pricing, are always built. A dynamic page is one file for many addresses: app/pages/journal/[slug].vue shows every post. Nitro finds its addresses by crawling: it renders /, follows every link in the HTML, renders those pages, and repeats. The home page links only to the posts marked listed in shared/utils/posts.ts, so the spring sale post, shared only in the newsletter, is never reached. Name such pages under nitro.prerender.routes in nuxt.config.ts.
What goes wrong
Both mistakes show up on a generated site, where no server runs. The first: a page nothing links to is missing.
nitro: {
prerender: {
// nothing named: the crawler only finds what the index links to
},
},
After nuxt generate, .output/public/journal holds hello-nuxt and oat-milk-latte, and no spring-sale. Served with npx serve .output/public, a small local file server, the newsletter link answers 404 with the 404.html shell. In a browser that shell loads the app and the post appears, so nobody notices. Search engines and link previews read the HTML without running JavaScript, and get the 404.
nitro: {
prerender: {
routes: ['/journal/spring-sale'],
},
},
The generate log now lists /journal/spring-sale among the initial routes, and the folder exists.
The second: a cache rule on a static host does nothing.
routeRules: {
'/pricing': { swr: 3600 }, // deployed with nuxt generate to a static host
},
nuxt generate renders /pricing once, so its rendered-at time and its prices stay as they were on build day. Either deploy the server build to a host that runs it:
nuxt build
node .output/server/index.mjs
With the server running, /pricing renders again once the hour is up. Or keep the static site and let the CMS start a build on every edit.
What to remember
routeRulespicks a mode per path:prerenderfor pages that change on deploy,swrfor pages that change sometimes,ssr: falsefor personal pages.swrserves the cached copy and renders a fresh one in the background once it is stale.isradds the CDN cache on Netlify and Vercel only.nuxt buildmakes a server for a Node host, shaped for other hosts by Nitro presets.NUXT_variables on the host change its settings with a restart, not a rebuild.nuxt generatemakes files only. A dynamic page is built when a link leads to it ornitro.prerender.routesnames it, and rules that need a server do nothing.
In your project
Do these in a real project. Each one says how to confirm it worked.- Run
npx nuxt generate, thennpx serve .output/public, and open/journal/spring-sale. The Network tab shows the document with status 404, View Source shows the empty shell, and the post still appears once the JavaScript runs. - Uncomment the
routesline innuxt.config.tsand generate again. Thespring-salefolder exists, and the page answers with status 200. - Run
npx nuxt build, thenNUXT_PUBLIC_SHOP_NAME="Harbour Shop" node .output/server/index.mjs. The header reads Harbour Shop without a rebuild. Reload/pricinga few times: the rendered-at time stays the same, and the response headers includecache-control: s-maxage=3600, stale-while-revalidate.