Vue Server-Side Rendering
Render Vue applications on the server for better performance
🖥️ What is SSR?
Server-Side Rendering generates HTML on the server instead of the browser. This improves initial load time, SEO, and provides better performance for users with slower devices or connections.
// Basic SSR setup
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'
const app = createSSRApp({
template: `<div>Hello SSR!</div>`
})
const html = await renderToString(app)
SSR Key Concepts
Faster Load
HTML ready immediately
// Server sends full HTML
<div id="app">
<h1>Content</h1>
</div>
Better SEO
Search engines see content
// Crawlers get full HTML
meta: {
title: 'Page Title',
description: '...'
}
Hydration
Make static HTML interactive
// Client takes over
app.mount('#app', true)
Performance
Better for slow devices
// Less client work
// Faster first paint
🔹 Basic SSR Setup
Create a simple server-side rendered Vue app:
// server.js
import express from 'express'
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'
const server = express()
server.get('*', async (req, res) => {
const app = createSSRApp({
data: () => ({ message: 'Hello SSR' }),
template: `<div>{{ message }}</div>`
})
const html = await renderToString(app)
res.send(`
<!DOCTYPE html>
<html>
<head><title>Vue SSR</title></head>
<body>
<div id="app">${html}</div>
</body>
</html>
`)
})
server.listen(3000)
🔹 Universal App Structure
Create an app that works on both server and client:
// app.js - Universal entry
import { createSSRApp } from 'vue'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
return { app }
}
// entry-client.js - Browser entry
import { createApp } from './app'
const { app } = createApp()
app.mount('#app')
// entry-server.js - Server entry
import { createApp } from './app'
import { renderToString } from 'vue/server-renderer'
export async function render() {
const { app } = createApp()
const html = await renderToString(app)
return html
}
🔹 Data Fetching in SSR
Load data on the server before rendering:
<!-- Article.vue -->
<script setup>
import { ref, onServerPrefetch } from 'vue'
const article = ref(null)
// Runs only on server
onServerPrefetch(async () => {
article.value = await fetchArticle()
})
// Runs on client if no SSR
if (!article.value) {
article.value = await fetchArticle()
}
</script>
<template>
<article v-if="article">
<h1>{{ article.title }}</h1>
<p>{{ article.content }}</p>
</article>
</template>
🔹 SSR with Router
Integrate Vue Router with server-side rendering:
// router.js
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router'
export function createAppRouter() {
return createRouter({
// Use memory history on server, web history on client
history: import.meta.env.SSR
? createMemoryHistory()
: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
})
}
// app.js
import { createSSRApp } from 'vue'
import { createAppRouter } from './router'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
const router = createAppRouter()
app.use(router)
return { app, router }
}
// entry-server.js
export async function render(url) {
const { app, router } = createApp()
await router.push(url)
await router.isReady()
const html = await renderToString(app)
return html
}
🔹 Using Nuxt.js (Recommended)
Nuxt provides SSR out of the box with zero configuration:
Why Use Nuxt?
- Automatic SSR: No manual setup needed
- File-based Routing: Pages from file structure
- Data Fetching: Built-in composables
- SEO: Easy meta tag management
- Deployment: Deploy anywhere easily
# Create Nuxt app
npx nuxi@latest init my-app
cd my-app
npm install
npm run dev
<!-- pages/index.vue - Auto SSR! -->
<script setup>
const { data: posts } = await useFetch('/api/posts')
</script>
<template>
<div>
<h1>Blog Posts</h1>
<article v-for="post in posts" :key="post.id">
<h2>{{ post.title }}</h2>
</article>
</div>
</template>
🔹 SSR Considerations
Important things to remember when using SSR:
⚠️ SSR Gotchas:
- No Browser APIs: window, document don't exist on server
- Lifecycle Hooks: Only beforeCreate and created run on server
- External Libraries: Must be SSR-compatible
- State Management: Create fresh store per request
- Performance: Server must handle rendering load
// Safe SSR code
import { onMounted } from 'vue'
// ❌ Don't do this
const width = window.innerWidth
// ✅ Do this instead
let width = 0
onMounted(() => {
width = window.innerWidth
})