Webpack 5 plugin for prerendering SPAs with puppeteer
npm install puppeteer-prerender-pluginThis is a Webpack 5 plugin for prerendering Single Page Applications (SPA) with Puppeteer. After Webpack emits all of your files, this plugin starts an Express static server in your dist directory. It then runs Puppeteer on all of your specified routes (e.g. /about) and saves the pages' rendered HTML as separate files (e.g. /dist/about/index.html).
The main benefit of prerendering your pages is for SEO benefits. Normally for an SPA, you would redirect all of your page requests to a single index.html and let your frontend framework handle routing. However, this also means that search engines will always see the same tags. By prerendering each route in your SPA, each page will be able to serve their respective tags for search engines.
* You are building a SPA that literally has one page.
* Hydration errors are difficult to debug.
* This will greatly increase your build times if you have a lot of routes to prerender (over 100+). Consider using Server Side Rendering (SSR) instead.
Option | Type | Example | Notes
--- | --- | --- | ---routes | Array | ['/pricing', '/'] | Required: Array of routes to render.entryDir | string | dist | Required: Directory to start the Express static server.entryFile | string | index.html (default) | Entry file for your SPA. Must be located in entryDir directory. This is useful if you do not want dist/index.html to be overwritten by the / route.publicPath | string | / (default) | Public path to serve static files from entryDir.outputDir | string | dist (defaults to entryDir) | Output directory for prerendered routes.enabled | boolean | false (default) | Disabled by default for performance. This option is useful if you wish to only prerender production builds e.g. process.env.NODE_ENV !== 'development'.keepAlive | boolean | false (default) | Enable this to keep the server alive after prerendering completes. You will need to manually terminate the shell command afterwards. This is useful if you wish to inspect the actual pages that Puppeteer has seen.maxConcurrent | number | 2 (defaults to routes.length) | Maximum number of concurrent Puppeteer instances. This option is useful for keeping CPU/memory usage down when you have a lot of routes.discoverNewRoutes | boolean | false (default) | Enable this to also prerender routes linked by a[href^=/] tags in rendered results.renderFirstRouteAlone | boolean | false (default) | Enable this to prerender the first route before rendering the rest concurrently. This is useful if you wish to cache the first route's state globally for future routes.injections | Array<{key: string, value: unknown}> | [{ key: 'isPrerender', value: true }] | Data to inject into each page with window[key] = value. This is useful if you wish to provide data to your app that's only present during prerender.renderAfterEvent | string | __RENDERED__ | Event name Puppeteer should wait for before saving page contents. You will need to manually dispatch the event in your app via document.dispatchEvent(new Event('__RENDERED__')).renderAfterTime | number | 5000 | Time in ms for Puppeteer to wait before saving page contents.postProcess | Function | See Example Usage | Function to post-process the saved page contents and route.puppeteerOptions | Object | See Example Usage | Options to pass to puppeteer.launch(). See Puppeteer documentation for more information.
> Important: Your / route must be defined last. Otherwise, routes rendered after / will use dist/index.html with artifacts specific to your homepage instead of a blank SPA index.html.
``ts
import { PuppeteerPrerenderPlugin } from 'puppeteer-prerender-plugin'
import HtmlWebpackPlugin from 'html-webpack-plugin'
export default {
target: 'web',
entry: 'main.ts',
plugins: [
new HtmlWebpackPlugin({
template: 'index.html', // Generates dist/index.html first
}),
new PuppeteerPrerenderPlugin({
enabled: process.env.NODE_ENV !== 'development',
entryDir: 'dist',
outputDir: 'dist',
renderAfterEvent: '__RENDERED__',
postProcess: (result) => {
result.html = result.html
.replace(/