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…