Skip to main content

Vue.js Static Site Generation

Introduction

Static Site Generation (SSG) is a technique that pre-renders your Vue.js application into static HTML files at build time. Unlike traditional Single Page Applications (SPAs) that render content in the browser, SSG generates all the HTML in advance, resulting in faster page loads and better SEO. This approach has become increasingly popular for content-heavy sites, blogs, documentation sites, and marketing pages.

In this guide, we'll explore how SSG works in Vue.js, why it matters for performance, and how to implement it using various tools and frameworks.

What is Static Site Generation?

Static Site Generation refers to the process of compiling your application into plain HTML, CSS, and JavaScript files during the build phase, rather than generating content on-demand when users visit your site.

Key Characteristics of SSG:

  • Pre-rendered HTML: Pages are generated at build time, not runtime
  • No server required: Can be hosted on simple static file servers
  • Fast initial load: Content is ready immediately when requested
  • SEO-friendly: Search engines can easily index the content
  • Enhanced security: Reduced attack surface with no server-side processing

Benefits of Static Site Generation for Vue.js Apps

1. Performance Improvements

SSG significantly reduces Time to First Contentful Paint (FCP) and Time to Interactive (TTI) metrics:

MetricTraditional SPASSG
Time to First Contentful PaintSlower (requires JS parsing)Faster (immediate HTML)
Time to InteractiveSlowerFaster
Server LoadHigherLower

2. Better SEO

Search engines can more easily crawl and index static HTML content compared to JavaScript-rendered content, leading to better search rankings.

3. Cost-Effective Hosting

Static sites can be hosted on inexpensive or even free content delivery networks (CDNs) like Netlify, Vercel, or GitHub Pages.

4. Improved Reliability

With fewer moving parts and dependencies, static sites are less prone to runtime errors.

Let's explore the most common approaches to implement SSG with Vue.js:

Nuxt.js

Nuxt.js is a full-featured framework for Vue that includes built-in SSG capabilities.

Basic Nuxt.js SSG Setup:

  1. First, install Nuxt.js:
bash
npx create-nuxt-app my-ssg-site
  1. Configure nuxt.config.js for static generation:
js
// nuxt.config.js
export default {
target: 'static',
// Other configuration...
}
  1. Generate your static site:
bash
npm run generate
  1. The output will be in the dist/ directory, ready for deployment.

Example: Creating a static blog post page

html
<!-- pages/blog/_slug.vue -->
<template>
<div class="blog-post">
<h1>{{ post.title }}</h1>
<div v-html="post.content"></div>
</div>
</template>

<script>
export default {
async asyncData({ params, $content }) {
const post = await $content('blog', params.slug).fetch()
return { post }
},
head() {
return {
title: this.post.title,
meta: [
{ hid: 'description', name: 'description', content: this.post.description }
]
}
}
}
</script>

With Nuxt's static generation, this dynamic page will be pre-rendered for each blog post at build time.

Vite SSG

Vite with vite-ssg provides a lightweight approach to SSG for Vue applications built with Vite.

Setting up Vite SSG:

  1. Install the necessary packages:
bash
npm install -D vite-ssg vite-plugin-vue
  1. Configure vite.config.js:
js
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
plugins: [vue()],
ssgOptions: {
script: 'async',
formatting: 'minify'
}
})
  1. Update your main entry file:
js
// main.js
import { ViteSSG } from 'vite-ssg'
import App from './App.vue'
import routes from './routes'

// Export the ViteSSG function
export const createApp = ViteSSG(
App,
{ routes },
({ app, router, isClient }) => {
// Custom initialization code here
}
)
  1. Add build scripts to package.json:
json
{
"scripts": {
"dev": "vite",
"build": "vite-ssg build",
"serve": "vite preview"
}
}

VuePress

VuePress is specifically designed for creating static documentation sites with Vue.js.

bash
# Install
npm install -D vuepress

# Create a markdown file
echo '# Hello VuePress' > docs/README.md

# Start writing
npm run docs:dev

# Build to static files
npm run docs:build

Implementation Strategies

1. Determining What to Pre-render

Not all pages need to be statically generated. Consider these questions:

  • How often does the content change?
  • Is the content personalized for users?
  • Is the page SEO-critical?

2. Managing Dynamic Content in Static Sites

Even with static generation, you can still have dynamic content through:

Hybrid Approach (Static + Client-side Hydration)

html
<template>
<div>
<!-- Static content -->
<h1>{{ product.name }}</h1>
<p>{{ product.description }}</p>

<!-- Dynamic content loaded client-side -->
<div v-if="loaded">
<p>Current stock: {{ stock }}</p>
<button @click="addToCart">Add to Cart</button>
</div>
<p v-else>Loading stock information...</p>
</div>
</template>

<script>
export default {
async asyncData({ params, $fetch }) {
// This runs at build time
const product = await $fetch(`/api/products/${params.id}`)
return { product }
},
data() {
return {
stock: null,
loaded: false
}
},
mounted() {
// This runs in the browser
this.fetchStockInfo()
},
methods: {
async fetchStockInfo() {
const response = await fetch(`/api/stock/${this.$route.params.id}`)
this.stock = await response.json()
this.loaded = true
},
addToCart() {
// Client-side cart functionality
}
}
}
</script>

3. Incremental Static Regeneration (ISR)

ISR is an extension of SSG that allows you to update static content after you've built your site. Nuxt.js supports this with its nuxt generate command and revalidate option:

js
// nuxt.config.js
export default {
target: 'static',
generate: {
interval: 1000, // 1 second interval between two pages generation
routes() {
return axios.get('https://my-api/posts')
.then(res => res.data.map(post => `/blog/${post.slug}`))
}
}
}

Real-World Example: Building a Documentation Site

Let's build a simple documentation site using Vue.js and Nuxt Content:

  1. Set up a new Nuxt project with content module:
bash
npx create-nuxt-app docs-site
cd docs-site
npm install @nuxt/content
  1. Add content module to nuxt.config.js:
js
// nuxt.config.js
export default {
target: 'static',
modules: [
'@nuxt/content'
],
content: {
markdown: {
prism: {
theme: 'prism-themes/themes/prism-material-oceanic.css'
}
}
}
}
  1. Create a documentation page:
markdown
// content/docs/getting-started.md
---
title: Getting Started
description: Learn how to start using our product
---

# Getting Started

Welcome to our product documentation.

## Installation

```bash
npm install awesome-product

Basic Usage

js
import { createAwesome } from 'awesome-product'

const awesome = createAwesome()
awesome.doSomething()

4. Create a page to display documentation:

```html
// pages/docs/_slug.vue
<template>
<div class="doc-page">
<aside class="sidebar">
<nav>
<ul>
<li v-for="doc in docs" :key="doc.slug">
<nuxt-link :to="`/docs/${doc.slug}`">{{ doc.title }}</nuxt-link>
</li>
</ul>
</nav>
</aside>

<main class="content">
<h1>{{ doc.title }}</h1>
<nuxt-content :document="doc" />
</main>
</div>
</template>

<script>
export default {
async asyncData({ $content, params }) {
const doc = await $content('docs', params.slug).fetch()
const docs = await $content('docs').only(['title', 'slug']).fetch()
return { doc, docs }
},
head() {
return {
title: this.doc.title,
meta: [
{ hid: 'description', name: 'description', content: this.doc.description }
]
}
}
}
</script>

<style>
.doc-page {
display: grid;
grid-template-columns: 250px 1fr;
gap: 2rem;
}
/* Additional styles... */
</style>
  1. Generate the static site:
bash
npm run generate

The result will be a fully static documentation site with excellent performance and SEO.

Performance Optimization Tips for SSG

1. Use Lazy Loading for Components

html
<template>
<div>
<h1>My Product Page</h1>
<!-- Lazy load heavy components -->
<client-only>
<ProductRecommendations v-if="showRecommendations" />
</client-only>
</div>
</template>

<script>
export default {
components: {
ProductRecommendations: () => import('../components/ProductRecommendations.vue')
},
data() {
return {
showRecommendations: false
}
},
mounted() {
// Lazy load after important content is visible
setTimeout(() => {
this.showRecommendations = true
}, 2000)
}
}
</script>

2. Optimize Images

Use image optimization tools and consider lazy loading large images:

html
<template>
<div>
<img
v-if="isVisible"
:src="image.src"
:alt="image.alt"
loading="lazy"
/>
<div v-else class="image-placeholder"></div>
</div>
</template>

<script>
export default {
props: {
image: Object
},
data() {
return {
isVisible: false,
observer: null
}
},
mounted() {
this.observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
this.isVisible = true
this.observer.disconnect()
}
})
this.observer.observe(this.$el)
},
beforeUnmount() {
if (this.observer) {
this.observer.disconnect()
}
}
}
</script>

3. Use PurgeCSS to Remove Unused CSS

For Nuxt.js, add PurgeCSS to minimize CSS size:

js
// nuxt.config.js
export default {
buildModules: [
'@nuxtjs/tailwindcss',
'nuxt-purgecss'
],
purgeCSS: {
enabled: ({ isDev, isClient }) => (!isDev && isClient),
paths: [
'components/**/*.vue',
'layouts/**/*.vue',
'pages/**/*.vue',
'plugins/**/*.js'
]
}
}

4. Optimize JavaScript Bundles

Split your JavaScript into smaller chunks:

js
// nuxt.config.js
export default {
build: {
splitChunks: {
layouts: true,
pages: true,
commons: true
}
}
}

Common Challenges and Solutions

1. Handling Authentication in Static Sites

For authenticated content, consider these approaches:

  1. Client-side authentication with protected routes
  2. JAMstack approach with serverless functions
  3. BFF (Backend for Frontend) pattern

Example of client-side authentication with protected content:

html
<template>
<div>
<div v-if="!authenticated">
<h2>Please log in to view this content</h2>
<LoginForm @login-success="onLoginSuccess" />
</div>

<div v-else>
<h2>Welcome, {{ user.name }}!</h2>
<ProtectedContent />
</div>
</div>
</template>

<script>
import { useAuth } from '../composables/auth'

export default {
setup() {
const { authenticated, user, login } = useAuth()

const onLoginSuccess = (credentials) => {
login(credentials)
}

return {
authenticated,
user,
onLoginSuccess
}
}
}
</script>

2. Handling Forms

For forms in static sites, use API endpoints or serverless functions:

html
<template>
<form @submit.prevent="submitForm">
<div class="form-group">
<label for="name">Name:</label>
<input id="name" v-model="form.name" type="text" required />
</div>

<div class="form-group">
<label for="email">Email:</label>
<input id="email" v-model="form.email" type="email" required />
</div>

<div class="form-group">
<label for="message">Message:</label>
<textarea id="message" v-model="form.message" required></textarea>
</div>

<button type="submit" :disabled="submitting">
{{ submitting ? 'Sending...' : 'Send Message' }}
</button>

<div v-if="success" class="success-message">
Thank you! Your message has been sent.
</div>

<div v-if="error" class="error-message">
{{ error }}
</div>
</form>
</template>

<script>
export default {
data() {
return {
form: {
name: '',
email: '',
message: ''
},
submitting: false,
success: false,
error: null
}
},
methods: {
async submitForm() {
this.submitting = true
this.error = null

try {
// This could be a serverless function endpoint
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.form)
})

if (!response.ok) {
throw new Error('Failed to send message. Please try again.')
}

this.success = true
this.form = { name: '', email: '', message: '' }
} catch (err) {
this.error = err.message
} finally {
this.submitting = false
}
}
}
}
</script>

Summary

Static Site Generation is a powerful technique for building high-performance Vue.js applications. By pre-rendering pages at build time, you can deliver fast-loading experiences, improve SEO, and reduce hosting costs.

Key takeaways:

  1. Performance benefits: SSG dramatically improves initial page load times
  2. SEO advantages: Static HTML is more easily indexed by search engines
  3. Implementation options: Tools like Nuxt.js, Vite SSG, and VuePress make SSG accessible
  4. Hybrid approaches: Combine static content with dynamic client-side features
  5. Optimization techniques: Further improve performance with lazy loading, code splitting, and asset optimization

As web performance becomes increasingly important for user experience and search rankings, SSG provides a compelling approach for content-heavy Vue.js applications.

Additional Resources

To further expand your knowledge about Vue.js static site generation:

Exercises

  1. Basic SSG Site: Create a simple portfolio website using Nuxt.js with static generation.
  2. Blog with Markdown: Build a blog that uses markdown files as the content source.
  3. E-commerce Product Pages: Implement a product catalog with static pages for products and dynamic cart functionality.
  4. Performance Comparison: Create the same small app using both traditional SPA and SSG approaches, then compare performance metrics.
  5. Advanced: Build a documentation site with search functionality that works without server-side processing.

By mastering Static Site Generation with Vue.js, you'll be able to create websites that are not only feature-rich but also blazing fast and SEO-friendly.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)