Vite Prerender Plugin
vite-prerender-plugin is a Vite plugin designed to perform server-side rendering (prerendering) of web applications to static HTML during the build process. This is particularly useful for improving initial page load performance and SEO for sites that don't require full server-side rendering on every request. The current stable version is `0.5.13`, with minor updates released somewhat regularly to address bug fixes and ensure compatibility with newer Vite versions, currently supporting Vite 5.x through 8.x. A key differentiator of this plugin is its highly flexible approach, being an extraction of the prerendering functionality from the Preact ecosystem's `@preact/preset-vite` and WMR. It delegates the actual rendering logic to the user, who must provide an exported `prerender()` function within a specified script. This function can be synchronous or asynchronous, allowing for data fetching and file system access, and is expected to return an object containing an `html` property with the rendered string. The plugin then injects this HTML into the main document at a configurable `renderTarget` selector, and can automatically discover and prerender additional routes.
Common errors
-
Cannot find module 'magic-string' (or similar Yarn dependency tree issues)
cause `magic-string` was briefly a peer dependency in `0.5.9`, causing resolution issues, particularly with Yarn's stricter dependency handling.fixUpgrade `vite-prerender-plugin` to version `0.5.10` or newer. This issue was resolved by reverting `magic-string` to a regular dependency. -
Cannot read properties of undefined (reading 'filename') in stack trace
cause An internal error handling source maps or stack traces when a filename was unexpectedly missing during the build process.fixThis issue was addressed in version `0.5.12`. Upgrade `vite-prerender-plugin` to `0.5.12` or later to apply the fix. -
Application not prerendered / blank content (during build or in output HTML)
cause The `prerenderScript` path is incorrect, the specified script does not export an `async function prerender(url: string): Promise<{html: string}>`, or the `renderTarget` option does not match your application's mount point.fixVerify that the `prerenderScript` path in `vite.config.ts` is absolute and correct. Confirm that the specified script explicitly exports the `prerender` function and that it returns an object with a non-empty `html` string. Also, ensure your `renderTarget` option matches your client-side app's root element ID/class.
Warnings
- gotcha The plugin requires a `prerender` function to be explicitly exported from a script, specified either via a `<script prerender>` attribute in your HTML or the `prerenderScript` plugin option. Failure to provide this will result in prerendering not occurring.
- gotcha The `renderTarget` plugin option must precisely match the query selector where your client-side application mounts. Incorrect configuration will lead to the prerendered content not being injected correctly into your HTML.
- breaking Vite peer dependency updates. Starting from `0.5.9`, the plugin explicitly requires Vite v5.x or newer, and since `0.5.13`, it supports Vite up to v8.x. Using incompatible Vite versions will result in build errors or unexpected behavior.
- gotcha In version `0.5.9`, `magic-string` was briefly moved to a peer dependency, which could cause Yarn-specific issues with duplicate installs or resolution problems. It was reverted to a regular dependency in `0.5.10`.
Install
-
npm install vite-prerender-plugin -
yarn add vite-prerender-plugin -
pnpm add vite-prerender-plugin
Imports
- vitePrerenderPlugin
const { vitePrerenderPlugin } = require('vite-prerender-plugin');import { vitePrerenderPlugin } from 'vite-prerender-plugin'; - PrerenderOptions
import type { PrerenderOptions } from 'vite-prerender-plugin'; - PrerenderResult
import type { PrerenderResult } from 'vite-prerender-plugin';
Quickstart
/* === 1. Install dependencies === */
// npm install vite vite-prerender-plugin
/* === 2. vite.config.ts === */
import { defineConfig } from 'vite';
import { vitePrerenderPlugin } from 'vite-prerender-plugin';
import { resolve } from 'path';
export default defineConfig({
plugins: [
vitePrerenderPlugin({
renderTarget: '#app', // Query selector for where to insert prerender result
prerenderScript: resolve(__dirname, './prerender.ts'), // Absolute path to your prerender script
additionalPrerenderRoutes: ['/about', '/contact', '/404'], // Routes not automatically discovered
}),
],
build: {
// Ensure build output is suitable for static hosting
// For this example, no special SSR options are needed beyond the plugin.
}
});
/* === 3. prerender.ts === */
// This script runs in a Node.js-like environment during the build process.
// You would typically use your framework's renderToString method here.
interface PrerenderResult { html: string; }
export async function prerender(url: string): Promise<PrerenderResult> {
let content = '';
switch (url) {
case '/':
content = '<h1>Home Page</h1><p>Welcome to the prerendered app!</p><a href="/about">About</a>';
break;
case '/about':
content = '<h1>About Us</h1><p>Learn more about our mission.</p><a href="/">Home</a>';
break;
case '/contact':
content = '<h1>Contact Us</h1><p>Get in touch with us.</p>';
break;
case '/404':
content = '<h1>404 - Page Not Found</h1><p>The page you requested does not exist.</p>';
break;
default:
content = '<h1>Dynamic Route</h1><p>This content was prerendered for: ' + url + '</p>';
break;
}
// Simulate an app mounting into a div with id 'app'
return { html: `<div id="app">${content}</div>` };
}
/* === 4. index.html === */
<!-- In your public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite Prerender Example</title>
</head>
<body>
<div id="app"><!-- Prerendered content will be injected here --></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
/* === 5. src/main.ts (client-side entrypoint) === */
// import './style.css'; // Optional: Basic styling
const app = document.querySelector<HTMLDivElement>('#app');
// Only hydrate or render if the content is not already prerendered
if (app && !app.innerHTML) {
app.innerHTML = `
<h1>Client-side Hydration</h1>
<p>This content is loaded dynamically if prerendering fails or for SPAs.</p>
<button id="counter">count is 0</button>
`;
let count = 0;
document.getElementById('counter')?.addEventListener('click', () => {
count++;
document.getElementById('counter')!.textContent = `count is ${count}`;
});
}