iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

SSR

Vue SSR: server-side rendering with Nuxt and the vanilla Vue SSR API. SEO, performance, hydration, and the patterns.

Vue — SSR

EXAMPLE
# ===== Why SSR =====
# - SEO (search engines see content immediately)
# - First Contentful Paint faster
# - Social media link previews
# - Pre-rendered static pages (SSG variant)

# Trade-offs: server compute, complexity, hydration mismatches.

# ===== Nuxt (recommended) =====
# Install:
npx nuxi init my-app
cd my-app
npm install
npm run dev

# Pages auto-route from pages/:
# pages/index.vue  ->  /
# pages/about.vue  ->  /about
# pages/users/[id].vue  ->  /users/:id

# Server data via useFetch + useAsyncData:
<script setup>
const { data: users } = await useFetch('/api/users');
</script>

<template>
  <ul>
    <li v-for="u in users" :key="u.id">{{ u.name }}</li>
  </ul>
</template>

# Runs on server first; hydrates on client.

# ===== Server routes =====
# server/api/users.get.ts
export default defineEventHandler(async (event) => {
  const users = await db.users.findMany();
  return users;
});

# Hits /api/users; available to useFetch + external clients.

# ===== SSG (static site generation) =====
# nuxt.config.ts
export default defineNuxtConfig({
  nitro: { prerender: { routes: ['/', '/about', '/blog'] } },
});

npm run generate
# Output: .output/public/ as static HTML

# ===== Hybrid =====
# Some pages SSR, some SSG, some SPA-only.
# nuxt.config.ts
routeRules: {
  '/': { prerender: true },               # SSG
  '/blog/**': { isr: 3600 },              # ISR cache 1h
  '/dashboard/**': { ssr: false },        # SPA
  '/api/**': { cors: true },
}

# ===== Vanilla Vue SSR (without Nuxt) =====
# Useful for embedded SSR; usually overkill.
import { createSSRApp } from 'vue';
import { renderToString } from 'vue/server-renderer';

const app = createSSRApp({
  data: () => ({ count: 1 }),
  template: '<div>{{ count }}</div>',
});

const html = await renderToString(app);

# Then serve + hydrate on client with createSSRApp + app.mount('#app').

# ===== Hydration =====
# Client picks up where the server left off:
# 1. Server renders HTML + serialised state
# 2. Browser loads JS bundle
# 3. Vue HYDRATES the existing DOM (attaches listeners)
# 4. Re-renders only if state diverges

# Hydration mismatches (server HTML !== client render) cause warnings.
# Common causes: random IDs, Date.now() in template, browser-only APIs.
# Fix with <ClientOnly> wrapper for inherently client-only content.

# ===== Performance =====
# - Cache SSR responses where appropriate (Vary on user, etc.)
# - Stream rendering (Vue 3 supports it) for faster TTFB
# - Edge SSR via Cloudflare / Vercel for low latency

# ===== Patterns =====
# - Nuxt for new SSR apps
# - useFetch + useAsyncData for SSR-friendly data fetching
# - Route rules for hybrid SSR/SSG/SPA
# - <ClientOnly> for browser-only components

# ===== Pitfalls =====
# - Window / document references during SSR (errors)
# - Hydration mismatches from non-deterministic content
# - State not transferred -> double-fetch
# - Heavy server logic on every request -> use cache + ISR

Why it matters

Vue SSR via Nuxt: file-based routing, useFetch for data, server/api routes for backend, route rules for SSR/SSG/SPA hybrid. The wins are SEO, FCP, link previews; the discipline is avoiding hydration mismatches and browser-only API leaks.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Nuxt handles SSR by default — every page is server-rendered.
// For custom SSR see vue.js.org/guide/scaling-up/ssr.
Try it Yourself »

Discussion

Loading…