Vue.js course Β· Module 15: TypeScript & Nuxt

SSR Concepts: SPA vs SSR vs SSG vs ISR

3 min read
In this lesson6

The NOVA LAB Command Center must deliver information as quickly as possible. Different rendering strategies are like different communication systems on the station - each has its advantages depending on the situation.

SPA - Single Page Application

A traditional Vue application is an SPA (Single Page Application):

  1. The browser downloads an empty HTML file
  2. It loads the JavaScript (Vue bundle)
  3. JavaScript renders the entire page in the browser
  4. Navigation happens without page reloads
1Server β†’ [empty HTML + JS bundle] β†’ Browser β†’ [rendering]

SPA Advantages:

  • Smooth navigation without reloads
  • Rich interactivity
  • Simple hosting (static files)

SPA Disadvantages:

  • Slow initial load (large JS bundle)
  • Empty page until JS loads
  • Poor SEO - bots see empty HTML

SSR - Server-Side Rendering

SSR renders the page on the server and sends ready HTML:

  1. The server receives a request
  2. Vue renders the component on the server to HTML
  3. It sends the ready HTML to the browser
  4. The browser displays the page immediately
  5. JavaScript is loaded and "hydrates" the page (adds interactivity)
1Request β†’ Server [renders HTML] β†’ Browser [displays + hydration]

SSR Advantages:

  • Faster First Contentful Paint (FCP)
  • Excellent SEO - bots see full HTML
  • Better performance on slow devices

SSR Disadvantages:

  • Higher server load
  • Longer Time to Interactive (TTI)
  • More complex infrastructure

SSG - Static Site Generation

SSG generates HTML at build time:

  1. During npm run build - each page is prerendered to HTML
  2. HTML files are ready to be served
  3. No server needed - plain static hosting
  4. JavaScript hydrates the page on the client
1Build time β†’ [generates HTML] β†’ Deploy β†’ CDN β†’ Browser

SSG Advantages:

  • Fastest possible response (files from CDN)
  • No server - cheapest option
  • Excellent SEO

SSG Disadvantages:

  • Content doesn't change between builds
  • Every change requires a rebuild
  • Not suitable for dynamic data

ISR - Incremental Static Regeneration

ISR combines SSG and SSR - pages are generated statically but can refresh:

  1. First visit - page served from static cache
  2. After a set time - the page is regenerated in the background
  3. Next visit gets the updated version
1Request β†’ [cache/CDN] β†’ if stale β†’ regenerate in background β†’ new version

ISR Advantages:

  • SSG speed + SSR freshness
  • Automatic refreshing

ISR Disadvantages:

  • More complex configuration
  • Data may be momentarily outdated (stale-while-revalidate)

Hydration - the Key to Interactivity

Hydration is the process where Vue "takes over" the static HTML generated by the server and adds interactivity to it:

1Server: <button>Click</button>  ← static HTML
2   ↓ hydration
3Client: <button @click="handler">Click</button>  ← interactive

Vue compares the virtual DOM with the existing HTML and attaches event handlers, refs, and reactivity - without re-rendering the entire DOM.

When to Use Which Strategy?

StrategyUse when...
SPAThe app requires rich interactivity (dashboard, editor)
SSRSEO matters + dynamic data (store, blog with comments)
SSGContent changes rarely (documentation, landing page)
ISRContent changes, but not in real time (blog, catalog)
Code for this lesson: App.vue
1<script setup lang="ts">
2import { ref } from 'vue'
3
4type RenderStrategy = 'SPA' | 'SSR' | 'SSG' | 'ISR'
5
6interface StrategyInfo {
7  name: RenderStrategy
8  fullName: string
9  description: string
10  pros: string[]
11  cons: string[]
12  useCase: string
13  color: string
14}
15
16const strategies = ref<StrategyInfo[]>([
17  {
18    name: 'SPA',
19    fullName: 'Single Page Application',
20    description: 'Rendering in the browser after JS loads',
21    pros: ['Smooth navigation', 'Rich interactivity', 'Simple hosting'],
22    cons: ['Slow loading', 'Weak SEO', 'Empty HTML'],
23    useCase: 'Dashboard, editor, internal application',
24    color: '#00b4d8'
25  },
26  {
27    name: 'SSR',
28    fullName: 'Server-Side Rendering',
29    description: 'The server renders HTML and sends a ready page',
30    pros: ['Fast FCP', 'Excellent SEO', 'Better performance'],
31    cons: ['Server load', 'Longer TTI', 'Complex infrastructure'],
32    useCase: 'Store, blog, social media',
33    color: '#00ff88'
34  },
35  {
36    name: 'SSG',
37    fullName: 'Static Site Generation',
38    description: 'HTML generated at build time (build)',
39    pros: ['Fastest response', 'No server', 'Excellent SEO'],
40    cons: ['Static content', 'Rebuild on change', 'Not for dynamic data'],
41    useCase: 'Documentation, landing page, portfolio',
42    color: '#9b59b6'
43  },
44  {
45    name: 'ISR',
46    fullName: 'Incremental Static Regeneration',
47    description: 'Static pages refreshed in the background',
48    pros: ['SSG speed + SSR freshness', 'Automatic refresh'],
49    cons: ['Complex configuration', 'Temporarily stale data'],
50    useCase: 'Blog, catalog, company website',
51    color: '#f39c12'
52  }
53])
54
55const selected = ref<RenderStrategy>('SPA')
56
57const current = ref<StrategyInfo>(strategies.value[0])
58
59function selectStrategy(name: RenderStrategy) {
60  selected.value = name
61  current.value = strategies.value.find(s => s.name === name)!
62}
63</script>
64
65<template>
66  <div class="nova-lab">
67    <h1>Rendering Strategies</h1>
68
69    <div class="tabs">
70      <button
71        v-for="s in strategies"
72        :key="s.name"
73        @click="selectStrategy(s.name)"
74        :class="{ active: selected === s.name }"
75        :style="{ borderColor: s.color }"
76      >
77        {{ s.name }}
78      </button>
79    </div>
80
81    <div class="detail" :style="{ borderColor: current.color }">
82      <h2 :style="{ color: current.color }">{{ current.name }}</h2>
83      <p class="fullname">{{ current.fullName }}</p>
84      <p class="desc">{{ current.description }}</p>
85
86      <div class="lists">
87        <div>
88          <h4 style="color: #00ff88">Pros</h4>
89          <ul>
90            <li v-for="pro in current.pros" :key="pro">{{ pro }}</li>
91          </ul>
92        </div>
93        <div>
94          <h4 style="color: #e74c3c">Cons</h4>
95          <ul>
96            <li v-for="con in current.cons" :key="con">{{ con }}</li>
97          </ul>
98        </div>
99      </div>
100
101      <div class="use-case">
102        <strong>Use case:</strong> {{ current.useCase }}
103      </div>
104    </div>
105  </div>
106</template>
107
108<style scoped>
109.nova-lab {
110  background: #0a0e27;
111  color: #00ff88;
112  min-height: 100vh;
113  padding: 20px;
114  font-family: monospace;
115}
116h1 { color: #00ff88; margin-bottom: 15px; }
117h2 { margin-bottom: 4px; }
118.tabs { display: flex; gap: 8px; margin-bottom: 15px; }
119.tabs button {
120  background: rgba(0,180,216,0.1);
121  color: #ccc;
122  border: 2px solid #333;
123  padding: 10px 20px;
124  border-radius: 8px;
125  cursor: pointer;
126  font-family: monospace;
127  font-weight: bold;
128}
129.tabs button.active {
130  background: rgba(0,180,216,0.25);
131  color: white;
132}
133.detail {
134  background: rgba(0,180,216,0.06);
135  border: 2px solid #00b4d8;
136  padding: 20px;
137  border-radius: 12px;
138}
139.fullname { color: #888; font-size: 13px; margin-bottom: 8px; }
140.desc { color: #ccc; margin-bottom: 12px; }
141.lists { display: flex; gap: 20px; margin-bottom: 12px; }
142.lists ul { list-style: none; padding: 0; }
143.lists li { padding: 3px 0; font-size: 13px; color: #ccc; }
144.lists li::before { content: '> '; color: #00b4d8; }
145h4 { margin-bottom: 6px; }
146.use-case {
147  background: rgba(0,255,136,0.08);
148  padding: 10px 14px;
149  border-radius: 8px;
150  font-size: 13px;
151  color: #00b4d8;
152}
153</style>

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What does SSR (Server-Side Rendering) mean?

  2. 2. What is hydration in the context of SSR?

These are 2 of 3 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Code editor

    Define a RenderStrategy union type and a StrategyInfo interface.

  • Vertical ordering

    Arrange the steps of the Server-Side Rendering process:

  • Code editor

    Define a CrewMember interface and type the data, pending, error states.

  • Vertical ordering

    Arrange the loading order in a SPA (Single Page Application):

  • Click in order

    Arrange rendering strategies from fastest to slowest First Contentful Paint:

  • Code editor

    Define a Page union type and type navigation between pages.

Useful articles