Hook — 05 : 41 a.m., Florianópolis ↔ Santo Domingo stand-up
Ocean mist billowed past my Brazilian hostel window as Ana, dialing in from Santo Domingo, sighed: “Our marketing microsite takes five seconds on 3 G and Google’s crawling half the pages.” Her SPA shipped client-side JavaScript only, forcing every visitor to download a chunky bundle before seeing any content. We pivoted on the spot—swapped the project to Nuxt’s Static Site Generation (SSG) mode, configured dynamic routes to pre-render 800 blog posts, and deployed to Netlify’s global edge. First paint dropped below one second, SEO warnings vanished, and Ana still made her morning kite-surf lesson. That tropical rescue anchors today’s exploration of SSG with Nuxt: why, when, and exactly how to configure it.
Why SSG Still Wins in 2025
Search engines, social previews, and bandwidth-limited users all prefer immediate HTML. Static pages:
- Eliminate time-to-first-byte latency — just a CDN fetch.
- Cut CPU cost in the browser — no runtime rendering overhead.
- Sidestep server bills — zero Node process to keep warm.
- Boost security — no server attack surface, only static assets.
Nuxt’s SSG pipeline lets us keep single-file components, Composition API hooks, and Vue.js devtools while delivering cache-friendly HTML. Perfect for blogs, docs, landing pages, catalogs, or documentation portals your distributed team can preview offline on shaky hostel Wi-Fi.
How Nuxt Generates Static Sites
- Build phase — Vite bundles Vue.js code.
- Prerender phase — Nuxt crawls every route, runs
useAsyncData
on the server, and saves the HTML + payload JSON todist/
. - Deploy phase — Upload the folder to Netlify, Vercel, GitHub Pages, S3 + CloudFront, or even an Nginx container.
- Runtime — The CDN serves complete pages; the browser hydrates them, turning static markup into live Vue components.
If content changes often, Nuxt’s Incremental Static Regeneration (ISR) lets you rebuild a single page on request—more on that below.
Project Setup from Scratch
bashCopyEditnpx nuxi init nuxt-ssg-demo
cd nuxt-ssg-demo
npm install
Enable SSG in config:
tsCopyEdit// nuxt.config.ts
export default defineNuxtConfig({
ssr: true, // keep true; SSG relies on SSR at build time
nitro: { preset: 'static' }, // hints for deploy platforms
});
Launch dev server:
bashCopyEditnpm run dev
Even in dev, Nuxt simulates SSG by prefetching data server-side.
Defining Static Routes
Nuxt automatically prerenders:
- All
pages/*.vue
files without dynamic params. - Dynamic params returned by
generate.routes
orprerender.routes
.
Example blog directory:
cssCopyEditcontent/
├─ post-1.md
├─ post-2.md
Tell Nuxt to generate each slug:
tsCopyEdit// nuxt.config.ts
import fs from 'node:fs';
export default defineNuxtConfig({
nitro: { preset: 'static' },
prerender: {
routes: fs
.readdirSync('content')
.map(f => '/blog/' + f.replace('.md', '')),
},
});
Run:
bashCopyEditnpm run generate
dist/
now contains /blog/post-1/index.html
, /blog/post-2/index.html
, plus /blog/post-1.payload.js
—hydration data inlined by Nuxt.
Hybrid Builds with Route Rules
Not every page suits SSG. Mix modes effortlessly:
tsCopyEditexport default defineNuxtConfig({
routeRules: {
'/blog/**': { static: true }, // full SSG
'/dashboard/**': { ssr: false }, // pure CSR (auth-gated)
'/': { static: true, swr: true }, // SSG + stale-while-revalidate
},
});
Edge functions rebuild SWR pages after cache expiry—great for product listings that change hourly, not per request.
Hands-On Example: Incremental Static Regeneration
Enable on-demand rebuild:
tsCopyEdit// server/api/revalidate.post.ts
export default defineEventHandler(async event => {
const body = await readBody(event);
if (body.secret !== process.env.REVALIDATE_TOKEN) throw createError({ status: 401 });
await $fetch('/api/_nitro/revalidate', { method: 'POST', body: { path: body.path } });
return { revalidated: body.path };
});
Webhook your CMS to call this endpoint on publish; Nuxt regenerates just that page, leaving the rest untouched.
Remote-Work Insight Box
During a sprint in Cartagena we migrated a 2 000-page docs site. Full builds once took 8 minutes—painful when power blipped. We flipped on ISR, regenerating only updated sections; merge-to-preview dropped to 30 seconds, making code reviews possible from cafés with diesel generator backup.
Performance & Accessibility Checkpoints
- Largest Contentful Paint should fall under 1.5 s on 3 G; confirm with Lighthouse on the generated site.
- Time to First Byte ≈ CDN latency (~30 ms regional).
- CLS stays near 0 because markup ships fully rendered.
- ARIA attributes appear in raw HTML—scrapers and screen readers see semantics even before hydration.
- Preload Hints — Nuxt auto-adds
<link rel="preload" as="script">
for critical JS; inspect head tags.
Common Pitfalls & Swift Fixes
Issue | Reason | Fix |
---|---|---|
Missing dynamic pages | Forgot to list routes | Add them in prerender.routes or use payloadExtraction: false |
Flash of undefined data | Using useFetch() (client) instead of useAsyncData() (SSR) | Swap composable—SSR renders first |
Large payload files | JSON too heavy | Normalize lists, compress images, lazy-load non-critical data |
404 on refresh for dynamic routes on Netlify | Missing redirect rules | Add _redirects with /* /index.html 200 when using browsers-history |
Call-Out Reference Table
Tool / Concept | One-liner Purpose |
---|---|
nuxi generate | Builds static site into /dist |
prerender.routes | Enumerate dynamic paths for SSG |
routeRules | Mix SSG, SSR, CSR per path |
ISR (swr:true ) | Rebuild page on first stale request |
nitro preset 'static' | Optimizes output for static hosts |
Must-Know CLI Commands
Command | Why You Need It |
---|---|
npm run generate | Produce static artifacts |
npx serve dist | Preview locally |
netlify deploy --prod | Push to global CDN |
nuxi cleanup | Remove stale .nuxt cache when switching modes |
Diagram Sketch (text)
Sanity publishes → webhook hits /api/revalidate
→ Nitro regenerates /blog/my-post
→ uploads to S3 → CloudFront invalidates single path → next visitor receives new HTML under 50 ms.
Wrap-Up
Static Site Generation pairs the developer joy of Vue.js with the speed and simplicity of plain HTML. By leaning on Nuxt’s declarative config—nitro preset
, prerender.routes
, and routeRules
—you can tailor rendering per page, shrink hosting bills, and delight users browsing on prepaid data plans from Mexico to Colombia.
Curious about ISR tokens, markdown pipelines, or edge deployments? Drop your questions below; I’ll respond between flights and late-night arepa sessions.