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

🧠 Test Your Knowledge

What is the main benefit of SSR?