Code Monkey home page Code Monkey logo

prerenderer's Introduction

Prerenderer

Fast, flexible, framework-agnostic prerendering for sites and SPAs.


test npm downloads js-standard-style license


Monorepo for the following NPM packages

  • npm version
  • npm version
  • npm version
  • npm version
  • npm version

Installation

Install your selected assortment of packages with your package manager

Example for Rollup or vite and the Puppeteer renderer:

npm install --save-dev @prerenderer/rollup-plugin @prerenderer/renderer-puppeteer puppeteer

Important

If your package manager does not install peer dependencies automatically, make sure to install puppeteer in your dependencies if you wish to use @prerenderer/renderer-puppeteer, there is different methods of installation but in the documentation we only cover the basic package installation, which will also install the necessary browsers binary

npm install --save-dev puppeteer

Then follow the installation instruction of your package of choice, that you can find in the README of that specific package

About prerenderer

The goal of this package is to provide a simple, framework-agnostic prerendering solution that is easily extensible and usable for any site or single-page-app.

Now, if you're not familiar with the concept of prerendering, you might predictably ask...

What is Prerendering?

Recently, SSR (Server Side Rendering) has taken the JavaScript front-end world by storm. The fact that you can now render your sites and apps on the server before sending them to your clients is an absolutely revolutionary idea (and totally not what everyone was doing before JS client-side apps got popular in the first place...)

However, the same criticisms that were valid for PHP, ASP, JSP, (and such) sites are valid for server-side rendering today. It's slow, breaks fairly easily, and is difficult to implement properly.

Thing is, despite what everyone might be telling you, you probably don't need SSR. You can get almost all the advantages of it (without the disadvantages) by using prerendering. Prerendering is basically firing up a headless browser, loading your app's routes, and saving the results to a static HTML file. You can then serve it with whatever static-file-serving solution you were using previously. It just works with HTML5 navigation and the likes. No need to change your code or add server-side rendering workarounds.

In the interest of transparency, there are some use-cases where prerendering might not be a great idea.

  • Tons of routes - If your site has hundreds or thousands of routes, prerendering will be really slow. Sure you only have to do it once per update, but it could take ages. Most people don't end up with thousands of static routes, but just in-case...
  • Dynamic Content - If your render routes that have content that's specific to the user viewing it or other dynamic sources, you should make sure you have placeholder components that can display until the dynamic content loads on the client-side. Otherwise, it might be a tad weird.

Example prerenderer Usage

(It's much simpler if you use prerenderer with webpack or another build system.)

Input

app/
├── index.html
└── index.js // Whatever JS controls the SPA, loaded by index.html

Output

app/
├── about
│   └── index.html // Static rendered /about route.
├── index.html // Static rendered / route.
├── index.js // Whatever JS controls the SPA, loaded by index.html
└── some
    └── deep
        └── nested
            └── route
                └── index.html // Static rendered nested route.
const fs = require('fs')
const path = require('path')
const mkdirp = require('mkdirp')
const Prerenderer = require('@prerenderer/prerenderer')
// Make sure you install a renderer as well!
const JSDOMRenderer = require('@prerenderer/renderer-jsdom')

const prerenderer = new Prerenderer({
  // Required - The path to the app to prerender. Should have an index.html and any other needed assets.
  staticDir: path.join(__dirname, 'app'),
  // The plugin that actually renders the page.
  renderer: new JSDOMRenderer(),
  postProcess (renderedRoute) {
    // Replace all http with https urls and localhost to your site url
    renderedRoute.html = renderedRoute.html.replace(
      /http:/ig,
      'https:',
    ).replace(
      /(https:\/\/)?(localhost|127\.0\.0\.1):\d*/ig,
      (process.env.CI_ENVIRONMENT_URL || ''),
    );
  },
})

// Initialize is separate from the constructor for flexibility of integration with build systems.
prerenderer.initialize()
  .then(() => {
    // List of routes to render.
    return prerenderer.renderRoutes(['/', '/about', '/some/deep/nested/route'])
  })
  .then(renderedRoutes => {
    // renderedRoutes is an array of objects in the format:
    // {
    //   route: String (The route rendered)
    //   html: String (The resulting HTML)
    // }
    renderedRoutes.forEach(renderedRoute => {
      try {
        // A smarter implementation would be required, but this does okay for an example.
        // Don't copy this directly!!!
        const outputDir = path.join(__dirname, 'app', renderedRoute.route)
        const outputFile = `${outputDir}/index.html`

        mkdirp.sync(outputDir)
        fs.writeFileSync(outputFile, renderedRoute.html.trim())
      } catch (e) {
        // Handle errors.
      }
    })

    // Shut down the file server and renderer.
    return prerenderer.destroy()
  })
  .catch(err => {
    // Shut down the server and renderer.
    return prerenderer.destroy()
    // Handle errors.
  })

Available Renderers

  • @prerenderer/renderer-jsdom - Uses jsdom. Fast, but unreliable and cannot handle advanced usages. May not work with all front-end frameworks and apps.
  • @prerenderer/renderer-puppeteer - Uses puppeteer to render pages in headless Chrome. Simpler and more reliable than the previous ChromeRenderer.

Which renderer should I use?

Use @prerenderer/renderer-puppeteer if: You're prerendering up to a couple hundred pages.

Use @prerenderer/renderer-jsdom if: You need to prerender thousands upon thousands of pages, but quality isn't all that important, and you're willing to work around issues for more advanced cases. (Programmatic SVG support, etc.)

An alternative faster dom renderer using linkedom is being considered

Documentation

All of the packages are strongly typed using typescript, if some documentation is missing or when in doubt, we recommend referring to the types which are self documenting

Prerenderer Options

Option Type Required? Default Description
staticDir String Yes None The root path to serve your app from. (If you are using a plugin, you don't need to set this, it will be taken from the configuration of webpack or rollup)
indexPath String No staticDir/index.html The index file to fall back on for SPAs.
server Object No None App server configuration options (See below)
renderer IRenderer Instance, constructor or String to require No new PuppeteerRenderer() The renderer you'd like to use to prerender the app. It's recommended that you specify this, but if not it will default to @prerenderer/renderer-puppeteer.
rendererOptions Object No None The options to pass to the renderer if it was not given as an instance, see below for a list of options
postProcess (renderedRoute: Route, routes: Route[]) => void No None Allows you to customize the HTML and output path before writing the rendered contents to a file, you can also add your own routes by pushing to the routes parameter

Server Options

Option Type Required? Default Description
port Integer No First free port after 8000 The port for the app server to run on.
proxy Object No No proxying Proxy configuration. Has the same signature as webpack-dev-server
host String No 127.0.0.1 The host to send requests to. Use with caution, as changing this could result in external urls being rendered instead of your local server, use postProcess if you just want to change urls in the resulting .html
listenHost String No 127.0.0.1 The ip address the server will listen to. (0.0.0.0 would allow external access, use with caution)
before Function No No operation Deprecated: Use hookServer() instead. Function for adding custom server middleware.

Prerenderer Methods

  • constructor(options: Object) - Creates a Prerenderer instance and sets up the renderer and server objects.
  • hookServer(cb: (server: Express) => void, stage: Stage = 'pre-fallback') - Use this method to hook into the express server to add middlewares, routes etc
  • initialize(): Promise<void> - Starts the static file server and renderer instance (where appropriate).
  • getOptions(): PrerenderFinalOptions - Returns the options used to configure prerenderer
  • getServer(): Server - Returns the Server class holding the express server
  • destroy(): Promise<void> - Destroys the static file server and renderer, freeing the resources.
  • renderRoutes(routes: Array<String>): Promise<Array<RenderedRoute>> - Renders set of routes. Returns a promise resolving to an array of rendered routes in the form of:
[
  {
    originalRoute: '/route/path', // The requested route path.
    route: '/route/redirected-path', // The final route path after redirection or history change.
    html: '<!DOCTYPE html><html>...</html>' // The prerendered HTML for the route
  },
  // ...
]

@prerenderer/renderer-jsdom Options

None of the options are required, by default the page will render on DOMContentLoaded

Option Type Default Description
maxConcurrentRoutes Number 0 (No limit) The number of routes allowed to be rendered at the same time. Useful for breaking down massive batches of routes into smaller chunks.
inject Object None An object to inject into the global scope of the rendered page before it finishes loading. Must be JSON.stringifiy-able. The property injected to is window['__PRERENDER_INJECTED'] by default.
injectProperty String __PRERENDER_INJECTED The property to mount inject to during rendering. Does nothing if inject isn't set.
renderAfterDocumentEvent String DOMContentLoaded Wait to render until the specified event is fired on the document. (You can fire an event like so: document.dispatchEvent(new Event('custom-render-trigger'))
renderAfterElementExists String (Selector) None Wait to render until the specified element is detected using document.querySelector
renderAfterTime Integer (Milliseconds) None Wait to render until a certain amount of time has passed.
timeout Integer (Milliseconds) 30000 If this timeout triggers while waiting for an event or an element, the rendering will abort with an error.
JSDOMOptions BaseOptions { runScripts: 'dangerously', resources: 'usable', pretendToBeVisual: true } Additional options for JSDOM.fromUrl

@prerenderer/renderer-puppeteer Options

None of the options are required, by default the page will render when puppeteer is ready which is when DOMContentLoaded fires

Option Type Default Description
maxConcurrentRoutes Number 0 (No limit) The number of routes allowed to be rendered at the same time. Useful for breaking down massive batches of routes into smaller chunks.
inject Object None An object to inject into the global scope of the rendered page before it finishes loading. Must be JSON.stringifiy-able. The property injected to is window['__PRERENDER_INJECTED'] by default.
injectProperty String __PRERENDER_INJECTED The property to mount inject to during rendering. Does nothing if inject isn't set.
renderAfterDocumentEvent String DOMContentLoaded Wait to render until the specified event is fired on the document. (You can fire an event like so: document.dispatchEvent(new Event('custom-render-trigger'))
renderAfterTime Integer (Milliseconds) None Wait to render until a certain amount of time has passed.
renderAfterElementExists String (Selector) None Wait to render until the specified element is detected using document.querySelector
elementVisible Boolean None If we should wait until the renderAfterElementExists is visible
elementHidden Boolean None If we should wait until the renderAfterElementExists is hidden
timeout Integer (Milliseconds) 30000 If this timeout triggers while waiting for an event or an element, the rendering will abort with an error.
skipThirdPartyRequests Boolean false Automatically block any third-party requests. (This can make your pages load faster by not loading non-essential scripts, styles, or fonts.)
headless Boolean true Whether to run the browser in headless mode
consoleHandler function(route: String, message: ConsoleMessage) None Allows you to provide a custom console.* handler for pages. Argument one to your function is the route being rendered, argument two is the Puppeteer ConsoleMessage object.
viewport Viewport None Those options will be passed to puppeteer.launch().
launchOptions LaunchOptions None Those options will be passed to puppeteer.launch().
navigationOptions WaitForOptions None Those options will be passed to page.goto(), such as timeout: 30000ms.

@prerenderer/webpack-plugin Options

Implementation details

The @prerenderer/webpack-plugin requires HtmlWebpackPlugin to be configured and serve your html as this plugin is hooked into it.

None of the options are required, by default the renderer-puppeteer will be used and render only the entry file

Option Type Default Description
routes Array ['/'] The list of routes to prerender
fallback Boolean String false
renderer string or instance of a renderer '@prerenderer/renderer-puppeteer' The instance of the renderer or the name of the renderer to require
rendererOptions Object None The options to pass to the renderer if it was not given as an instance, see above for a list of options
postProcess (renderedRoute: Route) => void None Allows you to customize the HTML and output path before writing the rendered contents to a file
urlModifier (url: string) => string None Hook to be able to modify the url to retrieve the compiled asset
entryPath String indexPath option The entry html file to use
... Additional Prerenderer Options

@prerenderer/rollup-plugin Options

Implementation details

The @prerenderer/rollup-plugin and @prerenderer/webpack-plugin aims to have the same feature set and api for an easy migration

As such the options are the same as the webpack-plugin

Caveats

  • For obvious reasons, prerenderer only works for SPAs that route using the HTML5 history API. index.html#/hash/route URLs will unfortunately not work.
  • Whatever client-side rendering library you're using should be able to at least replace any server-rendered content or diff with it.
    • For Vue.js 1 use replace: false on root components.
    • For Vue.js 2 and 3 Ensure your root component has the same id as the prerendered element it's replacing. Otherwise you'll end up with duplicated content.

Contributing

This is a monorepo, which uses lerna, so you'll need to clone the repository, then run npm install inside the directory

Run npm run test to make sure that everything is working correctly

Maintainers


Adrien Foulon

Joshua Bemenderfer

prerenderer's People

Contributors

a0s avatar b12f avatar boris-graeff avatar cbravo avatar davilima6 avatar dependabot[bot] avatar dpikt avatar elcontraption avatar ikatsuba avatar jocs avatar joshthederf avatar kushagharahi avatar marcusds avatar tofandel avatar zeronight avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

prerenderer's Issues

Handle timeout exception in JSDom Renderer

In JSDom renderer, we're using renderAfterElementExists property for prerendering. However in cases, where element doesn't exist/rendered(for some reason) some exception should be thrown after specific time interval to indicate that something went wrong while prerendering.

We checked the code and in such cases, Promise is not getting resolved in getPageContents() method shared below. Will it be possible to resolve the Promise after specific time interval and throw some exception.

https://github.com/Tribex/prerenderer/blob/3515c8386f1cfbf1581f2b64dd7d09af6ea86395/renderers/renderer-jsdom/es6/renderer.js#L19-L60

`injectProperty` does nothing if `inject` is unset.

It's more of a documentation enhancement than a bug.

If inject is unset, window.__PRERENDER_INJECTED will be undefined.

What's off-putting is that if injectProperty is expressly defined, window.yourInjectProperty will still be undefined.

I now simply use inject: true so that window.hasOwnProperty('__PRERENDER_INJECTED') === true.

renderAfterDocumentEvent for every page

Hi! Is there a way to "hold" every page before the Event is dispatched instead of waiting for the event and then prerender all the pages?

I need to wait for my API response in every page so I'm dispatching an event when the data has been passed to my component, but only the first page gets its content properly rendered.

I'm using the webpack plugin with Vue and axios.

Thanks in advance!

Renderer should not believe to window.location.pathname

I have vue3 project with path-based localization controlling.
When user go to /es/page inner script change locale to es (with updating translations) and update router to /page.
When i ask prerender routes: ['/es', '/es/page'] it creates /es and /page instead of /es and /es/page

I have a strong suspicion that this block of code should look like this:

const result: RenderedRoute = {
  originalRoute: route,
  route: route,  // <= should use initial `route` parameter
  html: await page.content(),
}

This can be changed directly, or done as a global option or a path-specific option (whether to trust window.location.pathname for a specific path or not). Unfortunately, I'm not a big expert on packaging npm packages and I can't quickly fix this for myself.

Make multiple renderAfter options available

Currently, if renderAfterDocumentEvent is set, renderAfterTime cannot be used anymore. The project I'm working on could really use a timed kill switch, where if the event doesn't fire after n seconds, just render and exit.

I'll probably pop out a PR tomorrow where I take the renderAfterTime out of the else if.

Bump version

Yo, I don't want to be pushy about this, but I'd like to add the proxy functionality to my project asap. Could you bump the version of @prerenderer/prerender here and as a dependency of prerender-spa-plugin#v3 ? Thanks so much dude!

page not clickable

env: vue2.6 webpack

how to code

I use a lot of v-for with <component :is > and mixin

    <component
      v-for="item in pageConfig.componentConfig"
      :id="item.id"
      :key="item.id"
      :is="item.component"
      :cid="item.componentId"
      :tags="item.tags"
      :uid="item.id"
      :uname="item.name"
      :ref="item.id"
      :styles="item.styles"
      :configuration-data="item.configurationData"
      :interval-data-fetch-list="item.intervalDataFetchList"
      :events="item.events"
      @component-event-emitted="handleComponentEvents(arguments, item.id)"
      @defined-event-emitted="handleDefinedEvents"
    >

the problems is:

  1. the page after prerenderer is not clickable
<div data-v-1a085488="" data-v-754880e6="" class="" id="f434c9b5-835c-4937-a5af-d70dcba100a9" style="display: block; height: 1.48rem; left: 0.54rem; position: absolute; top: 11.16rem; width: 6.36rem; word-break: break-all; z-index: 5; color: rgb(255, 255, 255); font-family: PingFangSC-Regular; font-size: 0.2rem; font-weight: normal; line-height: 0.34rem; text-align: left;"><p data-v-1a085488="" class="text-content-show" style="--backgroundColorHighlight: #638bc8; --backgroundColorGrey: #324995; --scrollBarWidth: 0.06rem;"><p data-v-1a085488="" style="white-space: pre-wrap;">     xxxxxxx  </p></p></div>

becomes

<div data-v-1a085488="" data-v-754880e6="" class="" id="1d907aed-0f35-42bf-95aa-bde716054ce0" style="display: block; height: 0.99rem; left: 0.75rem; position: absolute; top: 21.24rem; width: 6.04rem; word-break: break-all; z-index: 5; color: rgb(255, 255, 255); font-family: PingFangSC-Regular; font-size: 0.2rem; font-weight: normal; line-height: 0.34rem; text-align: left;"><p data-v-1a085488="" class="text-content-show" style="--backgroundColorHighlight: #638bc8; --backgroundColorGrey: #324995; --scrollBarWidth: 0.06rem;"></p><p data-v-1a085488="" style="white-space: pre-wrap;">      xxxxxxx </p><p></p></div>

as component bellow

<template>
  <div
    :style="[styles.base,styles.fontStyle]"
    :class="[{'text-box--hide':isCarouselAnimation}]"
    @click="handleClickEvent()"
    @mouseenter="handleEvent('hover')"
  >
    <p
      :class="configurationData.isShowScrollBar ? 'text-content-show' : 'text-content-hide'"
      :style="animateOrScrollBar"
    >
      <template v-if="Array.isArray(text)">
        <p
          v-for="(item, index) in text"
          :key="index"
          class="text-content-item"
          style="white-space: pre-wrap;"
        >
          {{ item }}
        </p>
      </template>
      <template v-else>
        <p style="white-space: pre-wrap;">
        {{ text }}
        </p>
      </template>
    </p>
  </div>
</template>

Navigation Timeout Exceeded: 30000ms exceeded

I'm trying to change this timeout value but not having any luck.

I see in the docs here it says:

Any additional options will be passed to page.goto(), such as timeout: 30000ms.

My config is quite simple and looks like this:

new Renderer({
    headless: true,
    timeout: 10000,
    renderAfterTime: 5000
})

That timeout just keeps showing the same error of

Navigation Timeout Exceeded: 30000ms exceeded

I'm using this plugin:

https://github.com/chrisvfritz/prerender-spa-plugin/blob/master/package.json

Which does seem to have the latest version, so seems it should be ok.

How can I change this?

Configuration to prerender alongside the standard build?

I managed to follow the readme to get my Vue 3 / Vite app prerendered, basically by adding:

prerender({
      routes: ["/"],
      renderer: "@prerenderer/renderer-puppeteer",
      postProcess(renderedRoute) {
        // Replace all http with https urls and localhost to your site url
        renderedRoute.html = renderedRoute.html
          .replace(/http:/i, "https:")
          .replace(
            /(https:\/\/)?(localhost|127\.0\.0\.1):\d*/i,
            process.env.CI_ENVIRONMENT_URL || ""
          );
      },
    }),

to my vite.config.ts. But this (expectedly) changes my regular vite build.

Is it possible to alter the config in a way that allows a regular build and a "prerender build" to coexist?

Typescript Typings issue with @prerenderer/rollup-plugin

Hi, I'm getting the following error when trying to build:

vite.config.ts:6:23 - error TS7016: Could not find a declaration file for module '@prerenderer/rollup-plugin'. '/Users/sabbirrahman/Projects/client/node_modules/@prerenderer/rollup-plugin/index.mjs' implicitly has an 'any' type.
  There are types at '/Users/sabbirrahman/Projects/client/node_modules/@prerenderer/rollup-plugin/types/index.d.ts', but this result could not be resolved when respecting package.json "exports". The '@prerenderer/rollup-plugin' library may need to update its package.json or typings.

6 import prerender from '@prerenderer/rollup-plugin';
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 1 error in vite.config.ts:6

If it helps, I'm using using [email protected] and [email protected].

Error 404

Trying to install prerender-spa-plugin returns a 404. Even trying to install @prerenderer/renderer-puppeteer returns 404

npm ERR! code E404
npm ERR! 404 Not Found: @prerenderer/renderer-puppeteer@^0.2.0

Session with given id not found

Lastly i have random error inside gitlab pipeline. Restarting does help sometimes, sometimes doesn't..

rendering chunks...
[plugin:Prerender Plugin] [plugin Prerender Plugin] Unable to prerender all routes!
.....
error during build:
RollupError: [Prerender Plugin] [plugin Prerender Plugin] Protocol error (Target.createTarget): Session with given id not found.
    at getRollupError (file:///app/node_modules/rollup/dist/es/shared/parseAst.js:396:41)
    at error (file:///app/node_modules/rollup/dist/es/shared/parseAst.js:392:42)
    at Object.error (file:///app/node_modules/rollup/dist/es/shared/node-entry.js:19589:20)
    at Object.<anonymous> (/app/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:140:34)
    at Generator.throw (<anonymous>)
    at rejected (/app/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:29:65)
    at runNextTicks (node:internal/process/task_queues:60:5)
    at process.processImmediate (node:internal/timers:449:9)

Another variant:

RollupError: [Prerender Plugin] [plugin Prerender Plugin] Protocol error: Connection closed. Most likely the page has been closed.
    at getRollupError (file:///app/node_modules/rollup/dist/es/shared/parseAst.js:396:41)
    at error (file:///app/node_modules/rollup/dist/es/shared/parseAst.js:392:42)
    at Object.error (file:///app/node_modules/rollup/dist/es/shared/node-entry.js:19589:20)
    at Object.<anonymous> (/app/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:140:34)
    at Generator.throw (<anonymous>)
    at rejected (/app/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:29:65)

I found similar errors in other repos:
cypress-io/cypress#28148
DevExpress/testcafe#7865

Versions:

"@prerenderer/renderer-puppeteer@npm:1.2.4"
"@prerenderer/rollup-plugin@npm:0.3.12"

UPD: I rechecked gitlab logs and found some errors like

src/router/index.ts(12,25): error TS7006: Parameter 'to' implicitly has an 'any' type.
src/router/index.ts(12,29): error TS7006: Parameter '_from' implicitly has an 'any' type.
src/router/index.ts(26,24): error TS7006: Parameter 'to' implicitly has an 'any' type.
src/router/index.ts(26,28): error TS7006: Parameter '_from' implicitly has an 'any' type.
src/router/index.ts(12,25): error TS7006: Parameter 'to' implicitly has an 'any' type.
src/router/index.ts(12,29): error TS7006: Parameter '_from' implicitly has an 'any' type.
src/router/index.ts(26,24): error TS7006: Parameter 'to' implicitly has an 'any' type.
src/router/index.ts(26,28): error TS7006: Parameter '_from' implicitly has an 'any' type.
[plugin:Prerender Plugin] [plugin Prerender Plugin] Unable to prerender all routes!
x Build failed in 26.76s

After i fix it, it come back to normal work.

jsdom test fails

Running npm run test:jsdom fails with an undefined window. Does a server need to be running the app already or should the jsdom renderer take care of that?

> @prerenderer/[email protected] test:jsdom /[path]/prerenderer
> jest ./test/**/*.jsdom.test.js --silent --runInBand


 RUNS  test/basic/basic.jsdom.test.js
/[path]/prerenderer/renderers/renderer-jsdom/es6/renderer.js:99
            window.addEventListener('error', function (event) {
                   ^

TypeError: Cannot read property 'addEventListener' of undefined
    at Object.created (/[path]/prerenderer/renderers/renderer-jsdom/es6/renderer.js:99:20)
    at reportInitError (/[path]/prerenderer/node_modules/jsdom/lib/old-api.js:472:12)
    at resourceLoader.download (/[path]/prerenderer/node_modules/jsdom/lib/old-api.js:291:9)
    at Request.request [as _callback] (/[path]/prerenderer/node_modules/jsdom/lib/jsdom/browser/resource-loader.js:208:7)
    at self.callback (/[path]/prerenderer/node_modules/request/request.js:186:22)
    at Request.emit (events.js:160:13)
    at Request.Object.<anonymous>.Request.onRequestError (/[path]/prerenderer/node_modules/request/request.js:878:8)
    at ClientRequest.emit (events.js:160:13)
    at Socket.socketErrorListener (_http_client.js:389:9)
    at Socket.emit (events.js:160:13)
    at emitErrorNT (internal/streams/destroy.js:64:8)
    at process._tickCallback (internal/process/next_tick.js:152:19)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! @prerenderer/[email protected] test:jsdom: `jest ./test/**/*.jsdom.test.js --silent --runInBand`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the @prerenderer/[email protected] test:jsdom script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /[path]/.npm/_logs/2018-02-27T16_24_49_190Z-debug.log

Vite + React

Hello, I'm using the latest version of Vite with React.

I'm encountering this error:

RollupError: index.html not found during prerender
and
error during build: RollupError: Could not prerender: event 'custom-render-trigger' did not occur within 30s.

I added

yarn add puppeteer
and
yarn add ts-deepmerge

which are required but not yet mentioned in the documentation. However, the issue persists.

And from that point onwards, all my routes were prerendered in the "dist" folder. GOOD JOB !

Route detection for a Vue 3 router

I currently hardcode the few dozen routes I have when setting routes in the config. Is there a way to generate this from a history API based router?

I realize prerenderer is meant to be framework agnostic, but I figured I'd ask before giving up.

index.mjs is missing in the published package

Hi,

I'm using @prerender/prerender in a .mjs file, and I got the error Unable to resolve path to module '@prerenderer/prerenderer'.

The cause is it tries to look out index.mjs as specified in exports.import in package.json, but there is no such file, only files: ["dist", "types"] specified in package.json. I think the index.mjs should be added into it.

Prerender detection

Hello! Is there any way for the app to tell whether it's being prerendered? I know that in react-snap you can check with navigator.userAgent === 'ReactSnap', so I'm wondering if there's an equivalent for this prerenderer. Thanks :)

renderAfterXMLHttpRequest

Hello,

I was thinking about a new waitForRender option, that we could call renderAfterXMLHttpRequest.
This would default to false, but when truthy, we could hook into XMLHttpRequest object, and using a configurable interval (defaulting to 500ms ?), we could check if there is any pending XHR.

What do you think ?

Ability to configure the server middleware

Hi, I'm trying to set up some custom middleware to be able to serve different HTML templates for different routes instead of defaulting to indexPath for a SPA. Is there a way I am able to add some custom middleware to the server? The only hooks I can see are modifyServer hooks that are not recommended to be used as they're internal.

Thanks for your help in advance!

suggest that server options add host

 const baseURL = `http://localhost:${rootOptions.server.port}`

change to

 const baseURL = `http://${rootOptions.server.host}:${rootOptions.server.port}`

I want to change the host, but cannot

Compatibility with @vue/cli-plugin-pwa

Hi,

I'm wondering if you have any advice on how I can make modifications such that the pre-rendered files get properly incorporated into the precache-manifest file that workbox creates. I'm happy to do the work, but some guidance would be greatly appreciated. My feeling is that the order of operations needs to worked out so that after pre-rendering, the workbox functionality creates the manifest with new hashes for the new route files, and the modified index.html file. Note: Using vue-cli-plugin-prerender-spa. Thanks!

Looking for maintainers!

As support for server-side rendering has matured, I've had little reason to revisit pre-rendering in any of my projects. As a result, I've had no need of this library for my own purposes and have let it fall by the wayside.

At this point, I'd be happy to transfer ownership of the project to someone who still has active use for pre-rendering and would be willing to bring this repo up back up to date.

Reply here if you're interested. :)

puppeteer-core renderer

I would like to run prerender-spa-plugin on zeit.co with puppeteer-core (and chrome-aws-lambda as chromium instance) instead of puppeteer to be able to generate my pages on zeit.co.

As explained in #42, chrome is not able to initialize itself on zeit.co because of a missing dependencies. I contacted zeit.co support which recommended me to use chrome-aws-lambda. However, it requires to use puppeteer-core instead of puppeteer.

Would it be possible to have a renderer which works with puppeteer-core instead of puppeteer?

renderAfterDocumentEvent does not get capture the event early enough

I currently have a vue / typescript project set up where I want to prerender two different routes:

// webpack.prod.conf.js

new PrerenderSpaPlugin({
        // Absolute path to compiled SPA
        staticDir: config.build.assetsRoot,
        // List of routes to prerender
        routes: [ '/', '/contact' ],

        renderer:  new PrerenderSpaPlugin.PuppeteerRenderer({
          renderAfterDocumentEvent: 'prerender-page-ready',
          injectProperty: '__PRERENDER_INJECTED',
          inject: {
            // Update src/window-shim.d.ts if you change this
            USER_ID: process.argv[2] || 'test-id',
            PRERENDER_PAGE_EVENT: 'prerender-page-ready'
          }
        })
      })

The app starts into the main App.vue component, which

  • loads data from an API asynchronously,
  • shows (with a v-if) the router-view when the call finishes,
  • and emits the event with name __PRERENDER_INJECTED.PRERENDER_PAGE_EVENT on document in the mounted hook of the components loaded by the router.

I have setup an eventlistener in the mounted hook of the App.vue, which works and logs the event when it comes in:

// App.vue

mounted() {
    console.log(PRERENDER_PAGE_EVENT);
    document.addEventListener(PRERENDER_PAGE_EVENT, (ev) => {console.log(ev)});
    axios.get(`${API_URL}/api/${ID}`)
      .then((res) => {
        this.title = res.data.title;
      })
      .catch(() => {
        this.title = 'An error occurred';
      });
  }

prerender-console

However, the prerenderer does not seem to take notice and the build hangs indefinitely. When I emit the event by hand from the devtools console, the respective tab closes and the correctly rendered version of the html is saved.

In the routed page components, changing the following:

  // Contact.vue

  mounted() {
    this.$nextTick(() => {
      document.dispatchEvent(new Event(PRERENDER_PAGE_EVENT));
    });
  }

To this:

  // Contact.vue

  mounted() {
    this.$nextTick(() => {
      setTimeout(() => {
        document.dispatchEvent(new Event(PRERENDER_PAGE_EVENT));
      }, 500);
    });
  }

Makes the build succeed, but feels very hacky.

Tested on Debian.

'renderer' does not exist in type 'RollupPrerenderOptions'

I've tried to use prerender function with renderer option.

prerender({ ... renderer: "@prerenderer/renderer-puppeteer", ... })

But RollupPrerenderOptions has no property named 'render' since it extends Omit<PrerendererOptions, 'staticDir' | 'renderer'>.

// node_modules/@prerenderer/rollup-plugin/types/Options.d.ts export interface RollupPrerenderOptions extends Omit<PrerendererOptions, 'staticDir' | 'renderer'> { entryPath?: string; fallback?: boolean | string; routes?: Array<string>; urlModifier?(url: string): string; }

So I can't use @prerenderer/renderer-puppeteer option as described in Readme.

How to use webpack-plugin with multiple entry

My project has multiple entry js files, and for each entry, there's a html-webpack-plugin configured with different output HTML name. Then how do I configure this plugin to prerender all the dist html? Should I also need to configure multiple prerender webpack plugin instances?

Catch error when Puppeteer is not able to initialize chrome

When using prerender-spa-plugin with Puppeteer renderer on zeit.co, Chrome is not able to properly initialize itself because of a missing system dependency. When it happens, an Promise rejection is send but the Puppeteer renderer fails to catch it, which leads the build not to properly terminal and fail.

I am not sure if the error should be catch by the renderer or by the plugin, so I post this issue here.

This can be easily reproduced by creating a project with prerender-spa-plugin and Puppeteer renderer and deploying it on zeit.co, either via git or via their now CLI.

Downloading 126 deployment files...
Installing build runtime...
Build runtime installed: 422.668ms
Looking up build cache...
Build cache found [30.53 MB, 27650 files]
Build cache unpacked: 1431.986ms
Installing dependencies...
yarn install v1.17.3
[1/4] Resolving packages...
[2/4] Fetching packages...
info [email protected]: The platform "linux" is incompatible with this module.
info "[email protected]" is an optional dependency and failed compatibility check. Excluding it from installation.
info [email protected]: The platform "linux" is incompatible with this module.
info "[email protected]" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning " > [email protected]" has unmet peer dependency "[email protected] - 3".
warning " > [email protected]" has unmet peer dependency "popper.js@^1.14.7".
warning " > [email protected]" has unmet peer dependency "webpack@^4.36.0".
warning " > [email protected]" has unmet peer dependency "webpack@^3.0.0 || ^4.0.0".
[4/4] Building fresh packages...
Done in 21.02s.
Running "yarn run build"
yarn run v1.17.3
$ vue-cli-service build

-  Building for production...
Starting type checking service...
Using 1 worker with 2048MB memory limit
Error: Failed to launch chrome!
/zeit/7433591d/node_modules/puppeteer/.local-chromium/linux-686378/chrome-linux/chrome: error while loading shared libraries: libXcomposite.so.1: cannot open shared object file: No such file or directory


TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md

    at onClose (/zeit/7433591d/node_modules/puppeteer/lib/Launcher.js:348:14)
    at Interface.helper.addEventListener (/zeit/7433591d/node_modules/puppeteer/lib/Launcher.js:337:50)
    at Interface.emit (events.js:203:15)
    at Interface.close (readline.js:397:8)
    at Socket.onend (readline.js:173:10)
    at Socket.emit (events.js:203:15)
    at endReadableNT (_stream_readable.js:1143:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)
[Prerenderer - PuppeteerRenderer] Unable to start Puppeteer
(node:544) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'close' of null
    at PuppeteerRenderer.destroy (/zeit/7433591d/node_modules/@prerenderer/renderer-puppeteer/es6/renderer.js:140:21)
    at Prerenderer.destroy (/zeit/7433591d/node_modules/@prerenderer/prerenderer/es6/index.js:87:20)
    at PrerendererInstance.initialize.then.then.then.then.then.then.then.then.catch.err (/zeit/7433591d/node_modules/prerender-spa-plugin/es6/index.js:144:29)
    at process._tickCallback (internal/process/next_tick.js:68:7)
(node:544) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:544) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

ERROR: Failed to set up Chrome v121.0.6167.85! Set "PUPPETEER_SKIP_DOWNLOAD" env variable to skip download

Screenshot from 2024-02-22 13-17-49

Issue Summary

Problem during deployment on Vercel

Description:
During the deployment of my application on Vercel, I encountered an error related to the Chrome setup. Specifically, I received an error indicating that the download of Chrome failed with error code 500.

Context:
I am using the "@prerenderer/renderer-puppeteer" package on VERCEL for my application. The error occurred during the deployment process on Vercel.

Error:

Error: ERROR: Failed to set up Chrome v121.0.6167.85! Set "PUPPETEER_SKIP_DOWNLOAD" env variable to skip download.
    at file:///vercel/path0/node_modules/puppeteer/lib/esm/puppeteer/node/install.js:58:23
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Promise.all (index 0)
    at async downloadBrowser (file:///vercel/path0/node_modules/puppeteer/lib/esm/puppeteer/node/install.js:87:9) {
  [cause]: Error: Download failed: server returned code 500. URL: https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/121.0.6167.85/linux64/chrome-linux64.zip
      at file:///vercel/path0/node_modules/@puppeteer/browsers/lib/esm/httpUtil.js:64:31
      at ClientRequest.requestCallback (file:///vercel/path0/node_modules/@puppeteer/browsers/lib/esm/httpUtil.js:42:13)
      at Object.onceWrapper (node:events:632:26)
      at ClientRequest.emit (node:events:517:28)
      at HTTPParser.parserOnIncomingClient (node:_http_client:700:27)
      at HTTPParser.parserOnHeadersComplete (node:_http_common:119:17)
      at TLSSocket.socketOnData (node:_http_client:541:22)
      at TLSSocket.emit (node:events:517:28)
      at addChunk (node:internal/streams/readable:335:12)
      at readableAddChunk (node:internal/streams/readable:308:9)
}

Additional Note:
The download URL provided in the error also returns a 500 error.

To resolve the issue:
✅ To resolve the issue, you can follow these steps:

  1. Ensure you have the correct configuration for Puppeteer. Since Puppeteer v19, Chrome is saved in ~/.cache/puppeteer instead of being inside node_modules.

  2. If some servers don't allow reading ~/.cache/puppeteer, it can cause the "Could not find Chrome" error. In this case, you'll need to move the .cache folder to the root of your project.

  3. DO NOT FORGET TO ADD .cache/ to YOUR GITIGNORE FILE

  4. Create a .puppeteerrc.cjs file at the root of your project with the following content:

const {join} = require('path');

module.exports = {
    // Changes the cache location for Puppeteer.
    cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
};

Ability to prerender via CLI

Hi,

I have a use case where I want to prerender a react app as part of a build pipeline for static deployment. I'm using parcel for the build step, which means I can't use e.g. prerender-spa-plugin. But your documentation made prerendering seem simple enough I was wondering - why not rig up a CLI script and run prerendering that way?

I'm not sure if this is the right place to tell you about this, but here's a proof of concept that works perfectly for my use case: https://github.com/Ezku/prerenderer-cli

In short, in my project I have a package.json like this:

"scripts": {
    "build": "yarn build:parcel && yarn build:prerender",
    "build:parcel": "yarn parcel build ./src/index.html",
    "build:prerender": "yarn prerender --renderer=jsdom --source=dist --target=dist --renderAfterDocumentEvent=react-render-complete index.html"
  },

Which yields parcel'd and prerender'd output in dist, the default output directory for parcel. I don't need to write any code in the project for this to happen because prerenderer-cli is quite enough.

What do you think? Is this interesting to you? Thanks for the great tool, I'm liking it a lot at this point.

Cheers,
Eevert

npm run build process doesn't end

Hi again :)

I ran into an issue running the plugin with npm run build (I'm using the default vue webpack template):

// package.json
"scripts": {
  "build": "node build/build.js",
}

//webpack.prod.conf.js
new PrerendererWebpackPlugin({
  staticDir: path.resolve(__dirname, '../dist'),
  routes: [ '/', '/category/some-category' ],
  renderer: new BrowserRenderer({
    renderAfterDocumentEvent: 'api-ready'
  }),
})

Everything works fine, builds the whole app and prerenders correctly, but the node process does not end:

captura de pantalla 2017-09-05 a les 11 16 17

It only happens when I load the prerenderer plugin. I also tried removing the renderAfter attributes but did not help.

Any ideas?

I'm on a Mac using node 8.2 and npm 5.4

Thanks again for your time!! 😇

Duplicate elements in <head>

The output of prerender appears fine on disk, e.g.:

<!DOCTYPE html>
<html lang="he" dir="rtl">

<head>
    <meta charset="UTF-8">
    <style type="text/css">
       ...
    </style>
    <link rel="icon" href="/assets/logo-189b1cf6.svg">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="module" crossorigin="" src="/assets/index-5bd76bc5.js"></script>
    <link rel="stylesheet" href="/assets/index-de5a37e2.css">
</head>

...

But when loading the prerendered page in a browser, some elements in <head> get duplicated, e.g. the <style>, and some <meta> (omitted for clarity).

Is this because the javascript in the page is essentially executed twice? Once by prerenderer, second time by the browser when loading the prerendered page? If so, how does the rest of the page look OK?

Thanks.

Some questions about the @pererenderer/rollup-plugin

My project is based on Vite packaging. Since my previous project was SPA, I ultimately hope that my project can be converted to SSG. Therefore, I started running npm install - save dev @ perenderer/rollup-plugin @ perenderer/renderer puppeteer puppeteer, and added import pre render from '@ perenderer/rollup-plugin in Vite. config. ts, with the following configuration:

rollupOptions: {
          plugins: [
            prerender({
              entryPath: 'index.html',
              renderer: '@prerenderer/renderer-puppeteer',
              rendererOptions: {
                  renderAfterDocumentEvent: 'custom-render-trigger',
                  headless: false
              },
              postProcess (renderedRoute) {
                // Replace all http with https urls and localhost to your site url
                renderedRoute.html = renderedRoute.html.replace(
                  /http:/ig,
                  'https:',
                ).replace(
                  /(https:\/\/)?(localhost|127\.0\.0\.1):\d*/ig,
                  (CDN_MAP[env] || ''),
                );
              },
            })
          ]
        }

But when I run vite build, the following error occurs:

Building [] 98% | Transforms: 4792/4792 | Chunks: 1/66 | Time: 15.5sPUPPETEER_DOWNLOAD_HOST is deprecated. Use PUPPETEER_DOWNLOAD_BASE_URL instead.
RollupError: index.html not found during prerender
    at error (file:///Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/es/shared/node-entry.js:2287:30)
    at Object.error (file:///Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/es/shared/node-entry.js:25351:20)
    at /Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/@[email protected]_7eidxhdof2pvopxq7j3qg5ehba/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:99:38
    at Layer.handle [as handle_request] (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/layer.js:95:5)
    at next (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/route.js:149:13)
    at Route.dispatch (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/route.js:119:3)
    at Layer.handle [as handle_request] (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/layer.js:95:5)
    at /Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:284:15
    at param (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:365:14)
    at param (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:376:14)
    at Function.process_params (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:421:3)
    at next (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:280:10)
    at expressInit (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/middleware/init.js:40:5)
    at Layer.handle [as handle_request] (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:328:13)
    at /Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:286:9
    at Function.process_params (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:346:12)
    at next (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:280:10)
    at query (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/middleware/query.js:45:5)
    at Layer.handle [as handle_request] (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:328:13)
    at /Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:286:9
    at Function.process_params (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:346:12)
    at next (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:280:10)
    at Function.handle (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/router/index.js:175:3)
    at Function.handle (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/application.js:181:10)
    at Server.app (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/express/lib/express.js:39:9)
    at Server.emit (node:events:527:35)
    at parserOnIncoming (node:_http_server:1143:12)
    at HTTPParser.parserOnHeadersComplete (node:_http_common:119:17)
Building [] 100% | Transforms: 4792/4792 | Chunks: 66/66 | Time: 60.1s

Build successful. Please see dist directory

error during build:
RollupError: Could not prerender: event 'custom-render-trigger' did not occur within 30s
    at error (file:///Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/es/shared/node-entry.js:2287:30)
    at Object.error (file:///Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/es/shared/node-entry.js:25351:20)
    at Object.<anonymous> (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/@[email protected]_7eidxhdof2pvopxq7j3qg5ehba/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:140:34)
    at Generator.throw (<anonymous>)
    at rejected (/Users/admin/yuanman_work/test_monorepo/ah5/increase/h5-web/node_modules/.pnpm/@[email protected]_7eidxhdof2pvopxq7j3qg5ehba/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:29:65)
 ELIFECYCLE  Command failed with exit code 1.

My index.html file is located in the project root directory, at the same level as vite.config.js. Why can't find it?

Allow to save state just before rendering

Hi, when prerendering certain applications it is sometimes important to be able to save some state on the client, for example if using React and loadable-components in order to avoid a dom diff and a re-render of the page we must save the loader state so we can later preload all our dynamic components that were loaded when prerendering the page. Another example could be to initialize a Redux store when prerendering but have it available on the client later.

For implementing this I see two different approaches:

  1. Let the user specify a beforeRender option that will execute the given global function just before rendering, in that global function the user is in charge of storing the data he needs in an script tag or a data attribute and to handle serialization properly.

  2. Provide a global function beforeRender that can be configured on the client by passing a callback that returns the application state. Then this object would be serialized and stored in a new script tag or in a data-attribute (to be compliant with csp) according to an optional second parameter.

What are your thoughts? I would be glad to P.R.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.