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.

Related Render Functions & SSR:renderToString()

1. What You'll Learn


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:

TEXT
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

TEXT
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)
JS
// 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:


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)

💡 This is a browser-runnable equivalent demo: real SSR only has a Node process and strings, so renderToString() builds the markup by hand here and it sits next to the real DOM Vue produces — same structure, two output paths.

▶ Try it Yourself

Output:

TEXT
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

BASH
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

TEXT
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)

TEXT
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

VUE
<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

VUE
<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

VUE
<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

TS
// 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

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)

VUE
<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

VUE
<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

TS
// 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
  ]
})
▶ Try it Yourself

Output:

TEXT
Nuxt installs and registers all five modules, then logs each one during startup:
TEXT
[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)

⚠️ Nuxt 3 is a Vite-based SSR framework; it must run on a Node server and cannot run from a CDN. The core structure:

TEXT
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:

TEXT
On startup Nuxt scans these directories and wires everything up automatically, printing the generated routes:
TEXT
[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:

TEXT
The block renders as a two-column reference table listing the five composables and their roles:
TEXT
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:

TEXT
The block renders as a two-column table that maps each scenario to the matching Nuxt mode:
TEXT
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:

TEXT
The block renders as a five-row troubleshooting table for the most frequent Nuxt pitfalls:
TEXT
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

Related Render Functions & SSR:renderToString()

❓ FAQ

Q CSR / SSR / SSG — which one?
A Content sites (blogs/docs) → SSG. E-commerce/social → SSR. Admin/SaaS → CSR. Nuxt 3 supports all three.
Q Which Node version does Nuxt 3 need?
A Node 18+ (since October 2023). Nuxt 2 still supports Node 16.
Q useFetch vs useAsyncData?
A useFetch is sugar on top of useAsyncData (specifically for fetch). In most cases useFetch is enough.
Q Nuxt 3 vs Nuxt 2?
A Nuxt 3 (November 2022) supports Vue 3 + Vite + TypeScript as first-class citizens. Nuxt 2 is maintenance-only.
Q How do I build an SSG site?
A npx nuxi generate produces the .output/public/ directory, which you deploy to Netlify / Vercel / Cloudflare Pages.
Q What if the SSR server is under heavy load?
A Deploy the Nitro server (built into Nuxt) to Vercel Edge / Cloudflare Workers / a Node cluster. Or downgrade to SSG.

📖 Summary


📝 Exercises

  1. Basic (Difficulty: ⭐) Create a Nuxt 3 project:

    • 3 pages (Home / About / Contact)
    • 1 API (/api/hello)
    • Top navigation + bottom layout
  2. 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
  3. Challenge (Difficulty: ⭐⭐⭐) Build a complete "enterprise-grade Nuxt 3" project:

    1. 10+ pages (e-commerce + admin + auth)
    2. 5 API routes
    3. 3 rendering modes (SSR / SSG / CSR mixed)
    4. SEO optimization (structured data + Open Graph + sitemap)
    5. Performance optimization (image lazy loading / code splitting / CDN)
    6. Deploy to Vercel Edge
    7. Complete TypeScript types
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏