npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

puppeteer-prerender-plugin

v3.0.18

Published

Webpack 5 plugin for prerendering SPAs with puppeteer

Downloads

23

Readme

Puppeteer Prerender Plugin

This 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).

Why?

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 <meta> tags. By prerendering each route in your SPA, each page will be able to serve their respective <meta> tags for search engines.

Why You Shouldn't Use This

  • 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.

Options

Option | Type | Example | Notes --- | --- | --- | --- routes | Array<string> | ['/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.

Example Usage (Vue 2)

webpack.config.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(/<script (.*?)>/g, '<script $1 defer>')
                    .replace('id="app"', 'id="app" data-server-rendered="true"')
            },
            routes: [
                '/pricing', // Renders to dist/pricing/index.html
                '/about',   // Renders to dist/about/index.html
                '/',        // Renders to dist/index.html
            ],
            puppeteerOptions: {
                // Needed to run inside Docker
                headless: 'new',
                args: [
                    '--no-sandbox',
                    '--disable-setuid-sandbox',
                ],
            },
        }),
    ],
}

main.ts

import Vue from 'vue'
import App from 'App.vue'

const app = new Vue(App)
app.mount('#app')

// Tell Puppeteer the page is ready to be saved
document.dispatchEvent(new Event('__RENDERED__'))

Vue 3 Usage

Vue 3 hydration assumes the markup has been rendered with @vue/server-renderer::renderToString function instead of the output markup of a normal SPA. This is due to the fact that renderToString outputs additional comment nodes. As a result, trying to hydrate non-SSR markup will result in hydration errors.

If you wish to prerender Vue 3 apps, you will need to set your postProcess callback to empty the <div id="app"> tag. Otherwise, you will see a "white flash" due to Vue replacing the prerendered markup with its client-rendered markup.

export default {
    plugins: [
        new PuppeteerPrerenderPlugin({
            postProcess: (result) => {
                const dom = new JSDOM(result.html)
                const app = dom.window.document.querySelector('div#app')
                if (app) {
                    // Remove app HTML since Vue 3 cannot hydrate non-SSR markup
                    app.innerHTML = ''
                }

                result.html = dom.serialize()
            },
        }),
    ],
}