Vue.js: SSR/SSG & Nuxt
Last updated: 2026-10-20
SSR (server-side rendering) and SSG (static site generation) are advanced deployment modes for Vue applications — they deliver a faster first screen and better SEO than CSR (client-side rendering). Nuxt 3 is the SSR framework for Vue 3, providing routing, state management, and SEO optimization out of the box.
Understanding the three rendering modes CSR/SSR/SSG lets you choose the right deployment mode for each scenario. This lesson builds up your SSR/SSG knowledge system.
1. What You'll Learn
- The three rendering modes compared: CSR vs SSR vs SSG
- The 5 main SSR use cases
- The complete Nuxt 3 directory structure
- Nuxt 3 composables (useState / useFetch / useAsyncData)
- Nuxt auto-imports + file-based routing
- The Nuxt module ecosystem (@nuxtjs/tailwindcss, etc.)
- 5 common mistakes
2. The "5-Second First Screen" Embarrassment of a Vue SPA
(1) Pain point: a slow CSR first screen + SEO that cannot see the content
Alice's Vue SPA ran into problems:
A traditional Vue SPA (CSR):
1. The browser requests → receives an empty HTML shell (<div id="app"></div>)
2. Downloads JS (500KB)
3. Executes JS (Vue boots)
4. Renders the content
5. The user sees the content
First screen: 3-5 seconds
SEO: Google cannot see the content (the SPA content is rendered by JS)
Product manager Charlie:
"Alice, our Google ranking is poor. We need server-rendered HTML so search engines can index the content. And we need a faster first screen."
(2) The Nuxt 3 SSR solution
Nuxt 3 SSR:
1. The browser requests → the server renders the complete HTML
2. Returns HTML that already contains the content (0.5s first screen)
3. Downloads JS (needed for hydration)
4. The client "activates" (takes over interactivity)
First screen: 0.5 seconds
SEO: ✅ Perfect (the HTML contains the content)
// server/api/products.js (Nuxt 3 automatic routing)
export default defineEventHandler(async (event) => {
return await $fetch('https://api.example.com/products')
})
(3) The payoff
After migrating to Nuxt 3 SSR:
- First screen: 3-5s → 0.5s (-90%)
- SEO score: 60 → 95 (+35)
- Google-indexed pages: 0 → all 5,000 SKU pages (+∞)
- Social sharing: 0 cards → full previews (Open Graph)
3. CSR vs SSR vs SSG
(1) The 3 rendering modes
| Mode | Render timing | First screen | SEO | Server load |
|---|---|---|---|---|
| CSR (client-side rendering) | Browser | Slow (3-5s) | ❌ Poor | Low |
| SSR (server-side rendering) | Every request | Medium (0.5-1s) | ✅ Good | High |
| SSG (static generation) | Build time | **Fast (0.1-0.5s) | ✅ Perfect | Lowest |
(2) The 5 main SSR use cases
| Scenario | Recommended mode |
|---|---|
| Content sites (blog / docs / marketing pages) | SSG (fastest + perfect SEO) |
| E-commerce product detail (5,000 SKUs) | SSR (live data + SEO) |
| SaaS admin (after login) | CSR (no SEO needed) |
| Social platforms (dynamic content) | SSR (real-time + SEO) |
| Personal blog (small amount of content) | SSG (simplest) |
(3) The 5 advantages of SSG over SSR
| SSG advantage | SSR advantage |
|---|---|
| Fastest (served straight from the CDN) | Live data (on every request) |
| Lowest server load | Personalized login state |
| Unlimited scaling (CDN) | Suited to dynamic content |
| No server needed after the build | Suited to interactive apps |
| Free hosting on Netlify / Vercel | Node.js server |
▶ Example: The Same Template Rendered Server-Side (string) and Client-Side (Vue) (browser-runnable)
renderToString() builds the markup by hand here and it sits next to the real DOM Vue produces — same structure, two output paths.
Output:
First render: region ① shows the server-built <ul> string (two li elements), region ② shows the same two products rendered by Vue — the tag hierarchy matches exactly.
The console prints both results: the string and the outerHTML line up, which is why the browser can paint the SSR result immediately and then let Vue take over (hydrate).
Click "add $10 to every price": region ② shows 109 / 139 while region ① stays put (it is the previous server response); click "Simulate a page reload" and the two line up again.
4. Nuxt 3 Basics
(1) Creating a project
npx nuxi@latest init my-nuxt-app
cd my-nuxt-app
npm install
npm run dev # defaults to http://localhost:3000
(2) The complete directory structure
my-nuxt-app/
├── assets/ # Assets (images, fonts)
│ └── css/
│ └── main.css
├── components/ # Shared components
│ ├── AppHeader.vue
│ └── AppFooter.vue
│ └── product/
│ └── ProductCard.vue # Nested directories are auto-registered
├── composables/ # Composables
│ └── useAuth.ts
├── layouts/ # Layouts
│ ├── default.vue # Default layout
│ └── admin.vue # Admin layout
├── middleware/ # Route middleware
│ └── auth.ts
├── pages/ # File routing (generated automatically)
│ ├── index.vue # /
│ ├── about.vue # /about
│ ├── products/
│ │ ├── index.vue # /products
│ │ └── [id].vue # /products/:id
│ └── admin/
│ └── index.vue # /admin
├── plugins/ # Nuxt plugins
│ └── pinia.ts
├── public/ # Static assets
├── server/ # Server-side code
│ ├── api/ # API routes (registered automatically)
│ │ └── products.ts # /api/products
│ └── middleware/ # Server middleware
├── stores/ # Pinia stores
├── app.vue # Root component
├── nuxt.config.ts # Nuxt config
└── package.json
(3) File routing (automatic)
pages/index.vue → /
pages/about.vue → /about
pages/products/index.vue → /products
pages/products/[id].vue → /products/:id
pages/admin/index.vue → /admin
No route table to configure — the file is the route.
5. Nuxt 3 Core APIs
(1) useFetch: server-side data prefetching
<script setup lang="ts">
// useFetch runs on the server during SSR and on the client during CSR
const { data, pending, error, refresh } = await useFetch('/api/products')
// The 5 returned values
// data: the response data
// pending: loading state
// error: error information
// refresh: a method to refetch
// status: the status code
</script>
(2) useAsyncData: general-purpose async data
<script setup lang="ts">
const { data } = await useAsyncData('products', () =>
$fetch('/api/products')
)
// Options: cache, dependencies, transform
const { data: users } = await useAsyncData(
'users',
() => $fetch('/api/users'),
{
cache: 'force-cache', // force caching
default: () => [] // default value
}
)
</script>
(3) useState: SSR-friendly state shared across components
<script setup lang="ts">
// useState replaces ref (SSR-friendly)
const cart = useState('cart', () => ({ items: [], total: 0 }))
// Mutating
cart.value.items.push(product)
</script>
(4) The 5 other composables
// 1. useFetch: server-side data prefetching
const { data } = await useFetch('/api/products')
// 2. useAsyncData: general-purpose async data
const { data } = await useAsyncData('key', fetcher)
// 3. useState: global state (SSR-friendly)
const user = useState('user', () => null)
// 4. useRoute: the current route
const route = useRoute()
// 5. useRuntimeConfig: runtime configuration
const config = useRuntimeConfig()
6. Nuxt 3 Core Configuration
(1) nuxt.config.ts
export default defineNuxtConfig({
// 1. Modules
modules: [
'@nuxtjs/tailwindcss',
'@pinia/nuxt',
'@vueuse/nuxt'
],
// 2. CSS
css: ['~/assets/css/main.css'],
// 3. Runtime configuration
runtimeConfig: {
apiSecret: 'xxx', // server-side
public: {
apiBase: 'https://api.example.com' // client-side
}
},
// 4. Rendering mode
ssr: true, // enable SSR
// 5. App configuration
app: {
head: {
title: 'My App',
meta: [
{ name: 'description', content: 'My app description' }
]
}
}
})
(2) Auto-imports (no import needed)
<script setup lang="ts">
// ✅ Nuxt auto-imports:
// - Vue 3 APIs (ref, computed, watch)
// - Nuxt 3 composables (useFetch, useState)
// - Components (components/ directory)
// - The utils/ directory
const count = ref(0)
const { data } = await useFetch('/api/products')
</script>
(3) Automatic SEO optimization
<script setup lang="ts">
useHead({
title: 'iPhone 15 Pro - My Shop',
meta: [
{ name: 'description', content: 'Buy iPhone 15 Pro at best price' },
{ property: 'og:title', content: 'iPhone 15 Pro' },
{ property: 'og:image', content: '/iphone.jpg' }
]
})
</script>
▶ Example: The 5 Nuxt Modules
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxtjs/tailwindcss', // Tailwind CSS integration
'@pinia/nuxt', // Pinia state management
'@vueuse/nuxt', // VueUse utilities
'@nuxt/image', // Image optimization
'nuxt-icon' // Icon system
]
})
Output:
Nuxt installs and registers all five modules, then logs each one during startup:
[Nuxt] ℹ Installing modules...
[Nuxt] ✔ @nuxtjs/tailwindcss
[Nuxt] ✔ @pinia/nuxt
[Nuxt] ✔ @vueuse/nuxt
[Nuxt] ✔ @nuxt/image
[Nuxt] ✔ nuxt-icon
[Nuxt] ✔ Vite server built in 3.2s
7. Putting It Together: Reading the Whole Project from Its Layout
▶ Example: Complete Nuxt Directory Structure (⚠️ Requires Vite + Nuxt)
my-nuxt-app/
├── pages/ # File routing (generated automatically)
├── components/ # Auto-registered components
├── composables/ # Auto-imported
├── server/api/ # API routes (server-side)
├── server/middleware/
├── middleware/ # Client middleware
├── plugins/ # Nuxt plugins
├── layouts/ # Layouts
└── nuxt.config.ts # Nuxt config
Output:
On startup Nuxt scans these directories and wires everything up automatically, printing the generated routes:
[Nuxt] ℹ Vite server warmed up in 412ms
[Nuxt] ✔ Auto-imports ready (vue, nuxt, components, composables)
[Nuxt] ✔ Routes generated:
/ → pages/index.vue
/about → pages/about.vue
/products → pages/products/index.vue
/products/:id → pages/products/[id].vue
/api/products → server/api/products.ts
(1) 📊 The 5 Core APIs (Nuxt auto-imports)
| API | Purpose |
|---|---|
useFetch |
Server-side data prefetching (SSR-friendly) |
useAsyncData |
General-purpose async data fetching |
useState |
Global state (SSR-friendly, auto-serialized) |
useRoute |
Current route information |
useRuntimeConfig |
Runtime configuration |
Output:
The block renders as a two-column reference table listing the five composables and their roles:
API Purpose
------------------ --------------------------------------------------
useFetch Server-side data prefetching (SSR-friendly)
useAsyncData General-purpose async data fetching
useState Global state (SSR-friendly, auto-serialized)
useRoute Current route information
useRuntimeConfig Runtime configuration
(2) 📊 The 5 Use Cases
| Scenario | Nuxt approach |
|---|---|
| Content site | SSG (nuxi generate) |
| E-commerce | SSR (default) |
| Admin | CSR (ssr: false) |
| Full stack | Nuxt + server/api |
| Static blog | SSG + Markdown |
Output:
The block renders as a two-column table that maps each scenario to the matching Nuxt mode:
Scenario Nuxt approach
------------- -----------------
Content site SSG (nuxi generate)
E-commerce SSR (default)
Admin CSR (ssr: false)
Full stack Nuxt + server/api
Static blog SSG + Markdown
(3) 📊 Quick Reference for 5 Common Mistakes
| Error | Symptom | Solution |
|---|---|---|
| useFetch data does not show up | Missing await | Change it to const { data } = await ... |
| SSR hydration fails | Data mismatch | useState instead of ref |
| Routing does not work | Wrong file path | Put the file in pages/ |
| Module installation fails | Wrong name | Check the modules spelling |
| SEO cannot see the page | ssr: false | Enable SSR |
Output:
The block renders as a five-row troubleshooting table for the most frequent Nuxt pitfalls:
Error Symptom Solution
--------------------------- ------------------- ------------------------------
useFetch data does not show Missing await const { data } = await ...
SSR hydration fails Data mismatch useState instead of ref
Routing does not work Wrong file path Put the file in pages/
Module installation fails Wrong name Check the modules spelling
SEO cannot see the page ssr: false Enable SSR
❓ FAQ
npx nuxi generate produces the .output/public/ directory, which you deploy to Netlify / Vercel / Cloudflare Pages.📖 Summary
- The three rendering modes: CSR (3-5s first screen) / SSR (0.5-1s) / SSG (0.1-0.5s)
- The 5 main SSR use cases: e-commerce / social / live data / login state / dynamic content
- Nuxt 3 is the official SSR framework for Vue 3
- File routing: the pages/ directory is the route table
- The 5 core APIs: useFetch / useAsyncData / useState / useRoute / useRuntimeConfig
- Auto-imports: Vue APIs + Nuxt composables + components + utils
- Nuxt module ecosystem: 200+ official/community modules
- Automatic SEO: dynamic tags via useHead
📝 Exercises
-
Basic (Difficulty: ⭐) Create a Nuxt 3 project:
- 3 pages (Home / About / Contact)
- 1 API (/api/hello)
- Top navigation + bottom layout
-
Intermediate (Difficulty: ⭐⭐) Implement the full Nuxt 3 SSR feature set:
- 5 pages (home / product list / detail / cart / login)
- server/api routes (/api/products)
- useFetch server-side prefetching
- useHead dynamic SEO
- Pinia state management
-
Challenge (Difficulty: ⭐⭐⭐) Build a complete "enterprise-grade Nuxt 3" project:
- 10+ pages (e-commerce + admin + auth)
- 5 API routes
- 3 rendering modes (SSR / SSG / CSR mixed)
- SEO optimization (structured data + Open Graph + sitemap)
- Performance optimization (image lazy loading / code splitting / CDN)
- Deploy to Vercel Edge
- Complete TypeScript types