Vite’s static site generation (SSG) setup is less about a magic "build" command and more about leveraging its existing build capabilities with a specific framework plugin.
Let’s see it in action with React and vite-plugin-react-ssg.
# First, set up a basic Vite + React project
npm create vite@latest my-ssg-app --template react
cd my-ssg-app
npm install
# Add the SSG plugin
npm install --save-dev vite-plugin-react-ssg
Now, let’s configure Vite. In your vite.config.js (or .ts):
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePluginReactSsg } from 'vite-plugin-react-ssg';
export default defineConfig({
plugins: [
react(),
VitePluginReactSsg({
// Options go here
// For example, to specify the entry point if it's not 'src/main.jsx'
// entry: 'src/main.jsx',
// To specify a different output directory
// outDir: 'dist-ssg',
// To specify routes to pre-render (if not all)
// routes: ['/', '/about', '/contact'],
}),
],
});
With this setup, running npm run build will now trigger the SSG process. Vite will bundle your React application, and vite-plugin-react-ssg will crawl your application’s routes (by default, it looks for Link components in your App.jsx or similar entry point) and render each page into a static HTML file.
The core problem SSG solves is the trade-off between client-side rendering (CSR) and server-side rendering (SSR). CSR gives you fast initial loads after the JavaScript is downloaded and executed, but search engines and social media crawlers often see a blank page. SSR solves this by rendering the HTML on the server for each request, but it incurs server costs and can introduce latency. SSG pre-renders all possible pages into static HTML files at build time. This means every page is instantly available as a fully rendered HTML document, offering the best of both worlds: excellent SEO, fast initial loads, and no server-side processing needed for content delivery.
Internally, vite-plugin-react-ssg works by intercepting Vite’s build process. It spins up a minimal Vite development server, uses a headless browser (like Playwright or Puppeteer) to visit each route defined or discovered, captures the rendered HTML output of your React components for that route, and saves it as a .html file in your output directory. This is why it’s crucial to have your routing set up correctly and for the plugin to be able to discover all the routes you intend to pre-render.
The primary lever you control is the routes option within the plugin configuration. If your application has dynamic routes (e.g., /users/:id), you’ll need to provide an array of specific paths you want to pre-render. For instance, if you have user pages for IDs '1', '2', and '3', you’d configure it like this:
VitePluginReactSsg({
routes: ['/', '/about', '/users/1', '/users/2', '/users/3'],
}),
This explicit declaration ensures that the plugin knows exactly which pages to generate. Without it, it might only find static links, missing dynamically generated content.
A common point of confusion is how to handle data fetching. For true SSG, all data needed for a page should ideally be fetched during the build process and then embedded into the HTML or served as static JSON files. The vite-plugin-react-ssg doesn’t automatically re-run your useEffect or data fetching functions during the build. You need to structure your components or use build-time data fetching utilities (like getStaticProps in Next.js, or manual fetching within your vite.config.js if necessary) to ensure the data is available when the page is rendered by the plugin’s headless browser. If your data is fetched client-side after the initial render, the static HTML won’t contain that data, and the plugin will only capture the loading state or initial empty content.
The next logical step after understanding SSG is exploring hybrid approaches, like Incremental Static Regeneration (ISR) or using SSG in conjunction with serverless functions for dynamic content.