Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 40 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,48 @@
# react-spa-prerender

The easiest way to prerender static pages, optimize SEO and build high performance for your React SPA.
Build production-ready code just by adding few lines of code.
* [Example of usage with create-react-app](https://github.com/sPavl0v/react-spa-prenderer/tree/master/examples/cra)
* [create-react-app + lazy loading](https://github.com/sPavl0v/react-spa-prenderer/tree/master/examples/cra-lazy)

- [Example of usage with create-react-app](https://github.com/sPavl0v/react-spa-prenderer/tree/master/examples/cra)
- [create-react-app + lazy loading](https://github.com/sPavl0v/react-spa-prenderer/tree/master/examples/cra-lazy)

### Upcoming features
* Auto sitemap generation
* Prebuilding pages with dynamic routes

- Auto sitemap generation
- Prebuilding pages with dynamic routes

Follow the steps below:

## Install

With npm

```
npm install react-spa-prerender --save-dev
```

With yarn

```
yarn add react-spa-prerender --dev
```

## Add as postbuild script

In your package.json add the following in the scripts section:

```
"scripts": {
"postbuild": "react-spa-prerender",
}
```

## Add .rsp.json file
__.rsp.json__ is the configuration file for `react-spa-prerender`. Create this file in your __application root folder__.
The minimum configuration requires the __routes__ you want to be parsed.

**.rsp.json** is the configuration file for `react-spa-prerender`. Create this file in your **application root folder**.
The minimum configuration requires the **routes** you want to be parsed.
Example:

```
{
routes: [
Expand All @@ -43,24 +54,29 @@ Example:
]
}
```

From example above:
Your "/" route will transform into "index.html" page.
"/about" -> "about.html"
"/services" -> "services.html"
"/blog/article1" -> create blog directory with file "article1.html" ("/blog/article1.html")
and so on...

__The rest of the .rsp.json options described below__
**The rest of the .rsp.json options described below**

## Add ReactDOM.hydrate in your index.js

In your index.js(main app file) change the ReactDOM.render logic:

```
import ReactDOM from 'react-dom';

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
```

Into following:

```
import ReactDOM from 'react-dom';

Expand All @@ -74,24 +90,26 @@ if (rootElement.hasChildNodes()) {
```

## Voila!!!

That's it. After accomplishing all the steps above, run you build command and your prerendered files will be in your build directory.

## .rsp.json Options

|option | type | default | description |
|-----|--------|------|---------|
| routes(Required) | Array | - | An array of routes you want to parse and prerender into static html|
| port | Number | 3000 | port where prerendering server will be starting |
| buildDirectory | String | './build' | a relative path to your build folder
|engine | Object | {} | params for Puppeteer engine, list of available params described below

| option | type | default | description |
| ----------------------- | ------ | --------- | --------------------------------------------------------------------- |
| routes(Required) | Array | - | An array of routes you want to parse and prerender into static html |
| urlParameters(Optional) | Array | - | An array of URL Parameter you want to pass along with Route |
| port | Number | 3000 | port where prerendering server will be starting |
| buildDirectory | String | './build' | a relative path to your build folder |
| engine | Object | {} | params for Puppeteer engine, list of available params described below |

### Engine options:

- launchOptions - object, containing properties to control **puppeteer.launch()** command. The whole list of available properties available here: [https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-puppeteerlaunchoptions](https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-puppeteerlaunchoptions)
- gotoOptions - object, navigation parameters. The whole list of available properties available here: [https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-pagegotourl-options](https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-pagegotourl-options)


#### Example of .rsp.json with engine options:

```
{
"port": 3000,
Expand All @@ -113,6 +131,12 @@ That's it. After accomplishing all the steps above, run you build command and yo
"/services",
"/blog/article1",
"/blog/article2"
],
"urlParameters": [
"?type=a",
"?type=b",
"",
"?demo=yes&tester=yes"
]
}
```
```
25 changes: 14 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async function readOptionsFromFile() {
* @param {string} dir
* @returns {string|boolean}
*/
async function runStaticServer(port, routes, dir) {
async function runStaticServer(port, routes, params, dir) {
try {
app = express();
const resolvedPath = resolve(dir);
Expand All @@ -41,7 +41,7 @@ async function runStaticServer(port, routes, dir) {

await app.listen(port);
return `http://localhost:${port}`;
} catch(err) {
} catch (err) {
throw new Error(`Error: Failed to run puppeteer server on port ${port}.\nMessage: ${err}`);
}
}
Expand All @@ -61,7 +61,7 @@ async function createNewHTMLPage(route, html, dir) {

const fileName = getValidatedFileName(route);

await fs.writeFileSync(`${dir}${fileName}`, html, {encoding: 'utf-8', flag: 'w'});
await fs.writeFileSync(`${dir}${fileName}`, html, { encoding: 'utf-8', flag: 'w' });
console.log(`Created ${fileName}`);
} catch (err) {
throw new Error(`Error: Failed to create HTML page for ${route}.\nMessage: ${err}`);
Expand All @@ -78,13 +78,13 @@ async function getHTMLfromPuppeteerPage(browser, pageUrl, options) {
try {
const page = await browser.newPage();

await page.goto(pageUrl, Object.assign({waitUntil: 'networkidle0'}, options));
await page.goto(pageUrl, Object.assign({ waitUntil: 'networkidle0' }, options));

const html = await page.content();
if (!html) return 0;

return html;
} catch(err) {
} catch (err) {
throw new Error(`Error: Failed to build HTML for ${pageUrl}.\nMessage: ${err}`);
}
}
Expand All @@ -96,16 +96,19 @@ async function getHTMLfromPuppeteerPage(browser, pageUrl, options) {
* @param {object} engine
* @returns {number|undefined}
*/
async function runPuppeteer(baseUrl, routes, dir, engine) {
async function runPuppeteer(baseUrl, routes, params, dir, engine) {
const browser = await puppeteer.launch(engine.launchOptions);
for (let i = 0; i < routes.length; i++) {
const parameters = params[i] || "";
console.log(`Processing route "${routes[i]}${parameters}"`);
if (parameters && !parameters.startsWith("?")) throw new Error(`Error: Failed to process parameters "${routes[i]}${parameters}"\nMessage: URL Parameters can be blank or must start with "?"`);

try {
console.log(`Processing route "${routes[i]}"`);
const html = await getHTMLfromPuppeteerPage(browser, `${baseUrl}${routes[i]}`, engine.gotoOptions);
const html = await getHTMLfromPuppeteerPage(browser, `${baseUrl}${routes[i]}${parameters}`, engine.gotoOptions);
if (html) createNewHTMLPage(routes[i], html, dir);
else return 0;
} catch (err) {
throw new Error(`Error: Failed to process route "${routes[i]}"\nMessage: ${err}`);
throw new Error(`Error: Failed to process route "${routes[i]}${parameters}"\nMessage: ${err}`);
}
}

Expand All @@ -115,11 +118,11 @@ async function runPuppeteer(baseUrl, routes, dir, engine) {

async function run(config) {
const options = config || await readOptionsFromFile();
const staticServerURL = await runStaticServer(options.port, options.routes, options.buildDirectory);
const staticServerURL = await runStaticServer(options.port, options.routes, options.urlParameters, options.buildDirectory);

if (!staticServerURL) return 0;

await runPuppeteer(staticServerURL, options.routes, options.buildDirectory, options.engine);
await runPuppeteer(staticServerURL, options.routes, options.urlParameters, options.buildDirectory, options.engine);
console.log('Finish react-spa-prerender tasks!');
process.exit();
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-spa-prerender",
"version": "1.0.14",
"version": "1.0.15",
"description": "React library for prerendering static pages, optimize SEO and web performance",
"main": "index.js",
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions utils/normalizeRspOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = function (options) {

return {
routes: options.routes || [],
urlParameters: options.urlParameters || [],
port: options.port || 3000,
buildDirectory: options.buildDirectory || './build',
engine
Expand Down