Rendering Modes
Next.js has Server Components, Client Components, static generation, ISR, and dynamic rendering. You choose per-component and per-route, and the interactions between these choices are where most of the complexity lives. "Why is this component rendering on the server when I expected it on the client?" is a rite of passage.
Nuxt defaults to universal rendering: every page renders on the server first, then hydrates on the client. You can change this globally or per-route. The mental model is simpler because there's one default and you only override it when you have a reason.
Outcome
Understand Nuxt's rendering modes and know when to override the default.
Fast Track
- Review the default rendering behavior (universal/SSR)
- Learn the
routeRulesconfig for per-route overrides - Identify which routes in the hot springs app benefit from each mode
Hands-on exercise 5.2
Analyze the rendering modes for each route in the app and configure route rules.
Requirements:
- Understand the three rendering modes: SSR (universal), CSR (client-only), and SSG (prerender)
- Identify which routes in the hot springs app are good candidates for each mode
- Add
routeRulestonuxt.config.tsto prerender the home page - Understand when CSR makes sense (and why we don't use it in this app)
Implementation hints:
routeRulesinnuxt.config.tsconfigures rendering per-route at the server level{ prerender: true }generates a static HTML file at build time{ ssr: false }disables server-side rendering for a route (client-only)- Route rules support glob patterns:
"/springs/**": { ... }applies to all spring detail pages
Here's how the three modes compare, translated from Next.js terms:
Nuxt Next.js equivalent When to use
──── ────────────────── ───────────
Universal (SSR) Dynamic rendering Default. Data changes per-request
Prerender (SSG) Static generation Content doesn't change between builds
Client-only (CSR) "use client" + no SSR Dashboard-style pages, auth-heavy UI
Our app is a good case study. Let's think about each route:
Home page (/): The content is static. No data fetching, no personalization. Prerender it. The HTML gets generated at build time and served as a static file. Fastest possible response.
Browse page (/springs): The content depends on filters, which depend on query parameters. SSR makes sense because the initial render should show all springs (good for SEO), and subsequent filter changes happen on the client.
Detail pages (/springs/:id): The spring data comes from a static JSON file. You could prerender all 17 detail pages at build time. But if the data changes (new reviews, updated descriptions), you'd need to rebuild. SSR is the safer default.
Auth pages (/favorites, /visited, /login): These are user-specific. Prerendering makes no sense because the content is different for every user. SSR works, but CSR would also work since these pages aren't crawled by search engines.
Let's configure the home page for prerendering:
import tailwindcss from "@tailwindcss/vite";
export default defineNuxtConfig({
compatibilityDate: "2025-05-01",
modules: ["nuxt-auth-utils"],
css: ["~/assets/css/main.css"],
vite: {
plugins: [tailwindcss()],
},
routeRules: {
"/": { prerender: true },
},
});That's it. At build time, Nuxt generates a static HTML file for / and serves it directly. No server-side rendering on each request. The rest of the app continues with universal rendering.
You could also prerender specific spring detail pages if you wanted:
routeRules: {
"/": { prerender: true },
"/springs/breitenbush-hot-springs": { prerender: true },
"/springs/blue-lagoon-iceland": { prerender: true },
},Or all of them with a crawl:
routeRules: {
"/": { prerender: true },
},
nitro: {
prerender: {
crawlLinks: true,
},
},crawlLinks follows every NuxtLink starting from the prerendered pages and prerenders everything it finds. From the home page, it would find /springs, then all 17 detail pages. Powerful, but it only works for content that exists at build time.
routeRules in nuxt.config.ts is a server-level config. It runs before your Vue code. You can also set definePageMeta({ ssr: false }) in a page file for client-only rendering. The difference: routeRules can set caching headers and other server behavior. definePageMeta is limited to rendering mode.
If you prerender /springs and the springs data changes after deployment, the page won't reflect the change until the next build. For our JSON-file-based app this doesn't matter, but it's a real consideration with databases. ISR (Incremental Static Regeneration) is available in Nuxt via routeRules with swr or isr options.
Try It
- Add the
routeRulesconfig tonuxt.config.ts - Run
pnpm buildand check the output. You should see/listed as a prerendered route - Run
pnpm previewand visit the home page. Check the response headers. The page should be served as static HTML - Visit
/springs. This should still be server-rendered (no prerendering)
Commit
git add -A && git commit -m "feat(perf): add route rules to prerender home page"Done-When
routeRulesis configured innuxt.config.tswith the home page set to prerender- You can explain the difference between SSR, CSR, and SSG in Nuxt terms
- You can identify which rendering mode is appropriate for each route in the app
- You understand how
crawlLinksworks for prerendering linked pages
Solution
import tailwindcss from "@tailwindcss/vite";
export default defineNuxtConfig({
compatibilityDate: "2025-05-01",
modules: ["nuxt-auth-utils"],
css: ["~/assets/css/main.css"],
vite: {
plugins: [tailwindcss()],
},
routeRules: {
"/": { prerender: true },
},
});Was this helpful?