Vue.js 73 🟢 Static Site Generation (SSG) with VitePress or Nuxt
VitePress and Nuxt both generate static HTML from Vue code, but they target different problems. VitePress is a documentation-first static site generator: Markdown files become pages, a default theme handles navigation and search, and the output is a content-centric site that ships almost no JavaScript beyond hydration . Nuxt is a full-stack Vue framework where static generation is one rendering mode among several: pages are Vue components, routing is file-based, data can be fetched from APIs at build time, and the same codebase can later switch to server-side rendering or hybrid modes . Choosing between them is not a performance decision—both produce fast static output—but a scope decision about how much framework you need around the content.
The practical difference appears immediately in project structure. A VitePress site is a docs/ folder of .md files plus a .vitepress/config.js; running vitepress dev renders Markdown with Vue components embedded inline . A Nuxt site is a pages/ directory of .vue files plus nuxt.config.ts; running nuxi generate crawls the routes and pre-renders each one to HTML, storing the result in .output/public . VitePress gives you a polished documentation theme with zero configuration. Nuxt gives you a blank canvas with routing, data fetching, and an ecosystem of modules.
This chapter covers VitePress’s Markdown-centric model and default documentation theme, Nuxt’s nuxt generate prerendering pipeline and its useFetch/useAsyncData data layer, the decision criteria that separate documentation sites from content-plus-application sites, and the deployment outputs each produces.
Key point: VitePress is for documentation and content sites where Markdown is the primary authoring format. Nuxt is for applications that happen to have static pages, or content sites that need dynamic data, complex routing, or a path to server-side rendering. Picking VitePress for an application means fighting the framework; picking Nuxt for pure documentation is overkill .
Why two SSGs exist for Vue
Documentation has a different shape than an application. A documentation site is a hierarchy of Markdown pages with a sidebar, a search box, and syntax-highlighted code blocks. The content changes rarely, the routing is predictable, and interactivity is limited to embedded examples. VitePress is built for this shape: its default theme ships with sidebar navigation, local search, dark mode, and code-group tabs out of the box . Building the same site in Nuxt requires assembling these features from modules or writing them yourself.
Applications need routing, data, and a path beyond static. A marketing site that fetches product data from a CMS, a blog with an index page generated from API responses, or a hybrid site with public static pages and authenticated application routes—these need more than Markdown rendering. Nuxt provides useFetch and useAsyncData for build-time data fetching, file-based routing for any page structure, and the ability to switch individual routes to server-side rendering later . VitePress can load remote data through its build APIs, but it does not provide a routing layer for application pages.
Both are Vue-team-blessed, but VitePress is the recommended SSG. The Vue team deprecated VuePress 1 and handed VuePress 2 to the community, focusing on VitePress as the long-term recommended static site generator . Nuxt is maintained by a separate team and is the Vue ecosystem’s full-stack framework. The two projects share tooling and philosophy but serve different layers of the stack.
The trade-off. VitePress produces smaller output for documentation because the default theme is optimized for content pages, not application shells. Nuxt produces a larger runtime because it includes the Nuxt framework, but that runtime is what enables routing, data fetching, and the option to add server routes later. The choice is about what the site needs to do after it is generated.
a. VitePress: Markdown as the source of truth
VitePress takes Markdown files, applies a theme, and generates static HTML. Each Markdown page is also a Vue Single-File Component, meaning Vue components can be imported and used directly inside the Markdown . This is the feature that separates VitePress from plain Markdown generators: a code example can be a live, interactive Vue component rather than a static string.
The project structure is minimal. A docs/ directory contains index.md (the home page) and any number of .md files organized into folders. The .vitepress/config.js file defines the site title, navigation, sidebar, and theme options. Running vitepress dev starts a Vite-powered development server with instant hot module replacement; running vitepress build produces static HTML in .vitepress/dist .
// .vitepress/config.js
export default {
title: 'My Project',
description: 'Documentation for My Project',
themeConfig: {
nav: [
{ text: 'Guide', link: '/guide/' },
{ text: 'API', link: '/api/' },
],
sidebar: {
'/guide/': [
{ text: 'Getting Started', link: '/guide/getting-started' },
{ text: 'Configuration', link: '/guide/configuration' },
],
},
},
};
A Markdown page can embed a Vue component directly. The component is hydrated on the client, so it can have state and event handlers. This is how VitePress documentation sites include interactive demos, API playgrounds, or live examples without leaving the Markdown authoring model.
# My Component
Here is a live example:
<MyInteractiveDemo />
The demo above is a Vue component imported in this page's script block.
VitePress serves static HTML on the first request, then hydrates into a Vue SPA for client-side navigation. Subsequent page changes do not reload the full page; VitePress prefetches linked page chunks and updates the content dynamically . This model gives the SEO benefits of static HTML with the navigation speed of a single-page application.
b. Nuxt: prerendering a Vue application
Nuxt’s static generation command is nuxi generate. It builds the application, starts a Nuxt instance, and crawls the routes: starting from /, following anchor tags, and prerendering every discoverable page to HTML. The output goes to .output/public, which can be deployed to any static hosting service . Pages that are not linked from a discoverable page are not prerendered automatically; they must be listed in prerender.routes or configured through routeRules .
The data layer is the key difference from VitePress. Nuxt provides useFetch and useAsyncData composables that run on the server during prerendering and on the client during navigation. A page that fetches data from a CMS at build time produces static HTML containing the fetched content; a page that fetches data on client navigation produces a loading state followed by the content . This is what enables Nuxt to generate a marketing site whose content lives in a headless CMS rather than in Markdown files.
// pages/blog/[slug].vue
<script setup>
const route = useRoute();
// During prerendering, this runs on the server and the result
// is embedded in the static HTML.
// During client navigation, it runs in the browser.
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`);
</script>
<template>
<article>
<h1>{{ post.title }}</h1>
<div v-html="post.content" />
</article>
</template>
Nuxt’s routing is file-based: pages/index.vue becomes /, pages/about.vue becomes /about, pages/blog/[slug].vue becomes /blog/:slug. This is the same routing model as Next.js and is familiar to developers coming from other full-stack frameworks . VitePress has no equivalent: its routes are derived from Markdown file paths, and there is no concept of dynamic route parameters or middleware.
The prerendering crawler has a specific behavior that matters for deployment: it follows links from the root page and from any pages listed in prerender.routes. A page that is reachable only through a form submission or a client-side redirect will not be prerendered unless explicitly listed. Nuxt also emits 200.html and 404.html fallbacks for SPA mode, which some hosting providers require for client-side routing .
c. Decision criteria: documentation versus application
The decision between VitePress and Nuxt follows a simple test. If the site is documentation, a blog, or a content site where Markdown is the primary authoring format and the navigation is a sidebar of pages, VitePress is the correct choice . It ships faster, produces smaller output, and provides a polished documentation theme without configuration.
If the site needs dynamic data at build time, complex routing (dynamic segments, nested layouts, middleware), a path to server-side rendering, or an application surface alongside the content, Nuxt is the correct choice . A marketing site with a CMS backend, an e-commerce product catalog, or a hybrid public/authenticated site all fall into this category.
A middle ground exists for content sites that outgrow VitePress’s theme but do not need Nuxt’s full framework. VitePress supports fully custom themes with the standard Vite + Vue development experience, and its build APIs allow loading data from local or remote sources . A team that wants VitePress’s Markdown authoring but needs a custom layout can build a theme without switching to Nuxt. The limitation is that VitePress still lacks Nuxt’s routing layer and data fetching composables; custom themes do not add application capabilities.
The ecosystem size reinforces the scope difference. Nuxt has 300+ modules for authentication, databases, SEO, and UI libraries . VitePress has a smaller plugin ecosystem focused on Markdown transformations and documentation features. A project that needs a module for its problem is more likely to find it in Nuxt’s ecosystem.
Complete Example Session
// ============================================
// PART 1: VITEPRESS PROJECT STRUCTURE
// ============================================
// docs/
// .vitepress/config.js
// index.md
// guide/getting-started.md
// guide/configuration.md
// ============================================
// PART 2: VITEPRESS CONFIGURATION
// ============================================
// .vitepress/config.js
// export default {
// title: 'My Docs',
// themeConfig: {
// sidebar: [
// { text: 'Guide', items: [
// { text: 'Getting Started', link: '/guide/getting-started' },
// ]},
// ],
// },
// };
// ============================================
// PART 3: VITEPRESS MARKDOWN WITH VUE
// ============================================
// guide/getting-started.md
// # Getting Started
//
// Install the package:
//
// ```bash
// npm install my-package
// ```
//
// <LiveDemo />
// ============================================
// PART 4: VITEPRESS BUILD AND DEPLOY
// ============================================
// npx vitepress build docs
// Output: docs/.vitepress/dist
// Deploy dist/ to GitHub Pages, Netlify, or any CDN.
// ============================================
// PART 5: NUXT PROJECT STRUCTURE
// ============================================
// pages/
// index.vue
// about.vue
// blog/[slug].vue
// nuxt.config.ts
// ============================================
// PART 6: NUXT PAGE WITH DATA FETCHING
// ============================================
// pages/blog/[slug].vue
// <script setup>
// const route = useRoute();
// const { data: post } = await useFetch(`/api/posts/${route.params.slug}`);
// </script>
// <template>
// <article>
// <h1>{{ post.title }}</h1>
// <div v-html="post.content" />
// </article>
// </template>
// ============================================
// PART 7: NUXT GENERATE
// ============================================
// npx nuxi generate
// Crawls routes from /, follows links, prerenders each page.
// Output: .output/public
// ============================================
// PART 8: NUXT SELECTIVE PRERENDERING
// ============================================
// nuxt.config.ts
// export default defineNuxtConfig({
// prerender: {
// routes: ['/sitemap.xml', '/robots.txt'],
// },
// });
// ============================================
// PART 9: THE DECISION
// ============================================
// Docs/blog/content in Markdown → VitePress.
// Dynamic data, routing, app surface → Nuxt.
// VitePress for pure docs is faster to ship.
// Nuxt for content + application is the right scope.
// ============================================
// PART 10: THE COMPLETE FLOW
// ============================================
// VitePress: Markdown → theme → static HTML.
// Nuxt: Vue pages → data fetch → crawl → static HTML.
// Both produce static output; scope determines the choice.
Quick Reference
VitePress vs Nuxt
| Aspect | VitePress | Nuxt |
|---|---|---|
| Primary use | Documentation, content sites | Full-stack apps, content + app |
| Authoring | Markdown (with Vue) | Vue components |
| Routing | File-based Markdown paths | File-based Vue pages |
| Data fetching | Build-time APIs | useFetch, useAsyncData |
| Default theme | Documentation theme included | No default theme |
| Ecosystem | Markdown plugins | 300+ modules |
| Build command | vitepress build | nuxi generate |
| Output | .vitepress/dist | .output/public |
When to Choose VitePress
| Scenario | Reason |
|---|---|
| Technical documentation | Default theme handles nav, search |
| Internal wiki | Markdown authoring, fast setup |
| Blog with Markdown posts | Content-centric, minimal config |
| Vue library docs | Ecosystem standard |
When to Choose Nuxt
| Scenario | Reason |
|---|---|
| CMS-backed marketing site | Build-time data fetching |
| E-commerce catalog | Dynamic routes, API data |
| Hybrid public/private site | Path to SSR or middleware |
| Content + application surface | Full framework capabilities |
Best Practices
✅ Do This:
// VitePress: use the default theme for docs. // ✅
// themeConfig.sidebar handles navigation.
// VitePress: embed Vue components in Markdown. // ✅
// <LiveDemo /> in .md file.
// Nuxt: use useFetch for build-time data. // ✅
const { data } = await useFetch('/api/posts');
// Nuxt: list non-linked routes in prerender.routes. // ✅
prerender: { routes: ['/sitemap.xml'] }
❌ Don’t Do This:
// Use VitePress for an application with auth. // ❌
// No routing layer or middleware support.
// Use Nuxt for pure Markdown documentation. // ❌
// Overkill; VitePress ships faster with less config.
// Assume Nuxt crawls unlinked routes. // ❌
// Pages not reachable by links need explicit listing.
// Build a custom theme in VitePress for an app. // ❌
// Fighting the framework; use Nuxt instead.
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Nuxt page not prerendered | Not linked from any crawled page | Add to prerender.routes |
| VitePress theme too limiting | Trying to build an app surface | Switch to Nuxt |
| Nuxt overkill for docs | Using full framework for Markdown | Use VitePress |
| VitePress data loading limited | Build APIs, no composables | Use Nuxt for dynamic data |
Real-World Examples
1. VitePress Config
export default { title: 'Docs', themeConfig: { sidebar: [...] } };
2. VitePress Build
npx vitepress build docs
3. Nuxt Generate
npx nuxi generate
4. Nuxt useFetch
const { data } = await useFetch('/api/posts');
5. Nuxt Prerender Routes
prerender: { routes: ['/sitemap.xml'] }
6. VitePress Vue Component in Markdown
<LiveDemo />
7. Nuxt Output
npx serve .output/public
8. VitePress Output
npx serve docs/.vitepress/dist
9. Nuxt SPA Fallback
// 200.html and 404.html emitted automatically
10. VitePress Search
// Default theme includes local search
Visual
VitePress Pipeline
┌──────────────────────────────────────────────┐
│ VITEPRESS BUILD │
│ │
│ docs/*.md │
│ │ │
│ ▼ │
│ Markdown → Vue SFC (Vite) │
│ │ │
│ ▼ │
│ Apply theme (default or custom) │
│ │ │
│ ▼ │
│ Static HTML + JS bundle │
│ │ │
│ ▼ │
│ .vitepress/dist │
│ │
│ Output: content-centric site │
│ Hydrates to SPA for client navigation. │
│ │
└──────────────────────────────────────────────┘
Nuxt Pipeline
┌──────────────────────────────────────────────┐
│ NUXT GENERATE │
│ │
│ pages/*.vue │
│ │ │
│ ▼ │
│ nuxt build (prerender: true) │
│ │ │
│ ▼ │
│ Crawl routes from / │
│ │ │
│ ▼ │
│ Fetch data (useFetch/useAsyncData) │
│ │ │
│ ▼ │
│ Render each route to HTML │
│ │ │
│ ▼ │
│ .output/public │
│ │
│ Output: application with static pages. │
│ Can switch to SSR later. │
│ │
└──────────────────────────────────────────────┘
Decision Tree
┌──────────────────────────────────────────────┐
│ WHICH SSG? │
│ │
│ Is the site primarily Markdown docs? │
│ ├── Yes → VitePress │
│ └── No → Does it need dynamic data │
│ or application routing? │
│ ├── Yes → Nuxt │
│ └── No → Could use either│
│ │
│ VitePress: faster to ship docs. │
│ Nuxt: more capability, more setup. │
│ │
└──────────────────────────────────────────────┘
Output Comparison
┌──────────────────────────────────────────────┐
│ VITEPRESS NUXT │
│ │
│ .vitepress/dist .output/public │
│ │
│ Small JS bundle Full Nuxt runtime │
│ Documentation theme No default theme │
│ Markdown routes Vue page routes │
│ │
│ Best for: Best for: │
│ - Docs - Marketing sites │
│ - Blogs - E-commerce │
│ - Wikis - Hybrid apps │
│ │
└──────────────────────────────────────────────┘
Summary
| Item | VitePress | Nuxt |
|---|---|---|
| Authoring format | Markdown | Vue components |
| Default theme | Documentation theme | None |
| Data fetching | Build APIs | useFetch/useAsyncData |
| Routing | Markdown file paths | File-based Vue pages |
| Prerendering | All Markdown pages | Crawled routes |
| Ecosystem | Markdown plugins | 300+ modules |
| Output | .vitepress/dist | .output/public |
| Best for | Documentation | Content + application |
Key takeaways:
- VitePress is documentation-first; Nuxt is application-first. VitePress takes Markdown, applies a theme, and generates a content site. Nuxt takes Vue pages, fetches data, and prerenders an application .
- VitePress ships with a documentation theme. Sidebar, search, code blocks, and dark mode are configured out of the box. Nuxt provides no default theme; you build or install one .
- Nuxt’s data layer is its differentiator.
useFetchanduseAsyncDatarun during prerendering, embedding fetched content in the static HTML. VitePress can load data through build APIs but does not provide composables . - Nuxt’s routing is file-based Vue pages. Dynamic segments, middleware, and nested layouts are supported. VitePress routes are Markdown paths with no dynamic parameters .
- Both are valid static site generators. The choice is scope: VitePress for content in Markdown, Nuxt for applications with static pages or content that needs dynamic data .
- VitePress is the Vue team’s recommended SSG for documentation. VuePress 1 is deprecated, and VitePress is the long-term focus. Nuxt is maintained separately as the full-stack framework .
Remember: VitePress and Nuxt both produce static HTML, but they start from different premises. VitePress starts from Markdown and asks what theme to apply. Nuxt starts from a Vue application and asks which routes to prerender. A documentation site is a collection of Markdown pages that need navigation and search; VitePress provides that with minimal configuration. A marketing site or blog with a CMS backend is an application that fetches data and renders pages; Nuxt provides the data layer, routing, and prerendering pipeline. Choosing VitePress for an application means fighting the framework’s content-first assumptions. Choosing Nuxt for pure documentation means carrying a full framework for a problem that a Markdown theme already solves.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!