react项目预渲染开发

react越来越火了,是开react开发的人员而是越来越多。但是因为单页应用SEO的问题,我们也不得不去解决这个问题。不管是哪里,都提供了两种方案,一种是SSR服务端渲染,另一种则是预渲染方式。本篇文章主要是阐述预渲染的方案。

什么是预渲染

在一般的react项目中(比如使用create-react-app创建的项目),我们在最后打包的时候只会生成一个HTML,JS与CSS文件,或许你会采用一些方法,比如公共文件拆分,路由懒加载等等生成多个文件,但是也无法从根本上解决这个问题,所有的资源还是通过JS动态的生成渲染的。

所以,所谓的预渲染就是在单页应用中,将用户交互不多,同时需要SEO的页面单独提取出来的一种方法,提取出来的就是一个HTML文件。

怎么使用预渲染

开发react项目的时候,一般都是结合者webpack使用的。目前用的最多的预渲染的方法,就是使用webpack插件prerender-spa-plugin。这是一个webpack插件,所以使用直接在webpack的插件配置项中添加

1
2
3
4
5
6
7
new PrerenderSPAPlugin({
routes: ["/", "/download", "/prize", "/news", "/news/detail?id=1", "/support"],
staticDir: path.join(__dirname, 'build'),
renderer: new Renderer({
renderAfterTime: 50000
})
})

其中routes是需要预渲染的route,一般都是react-router-dom配置的路由。 staticDir是输出的目录。因为这里使用了create-react-app,默认的输出目录是build,所以,这里也是build,如果你是自己搭建或者使用的是其他的方式,或许目录名字会有所不同。

更多具体的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// 摘取自github
new PrerenderSPAPlugin({
// Required - The path to the webpack-outputted app to prerender.
staticDir: path.join(__dirname, 'dist'),

// Optional - The path your rendered app should be output to.
// (Defaults to staticDir.)
outputDir: path.join(__dirname, 'prerendered'),

// Optional - The location of index.html
indexPath: path.join(__dirname, 'dist', 'index.html'),

// Required - Routes to render.
routes: [ '/', '/about', '/some/deep/nested/route' ],

// Optional - Allows you to customize the HTML and output path before
// writing the rendered contents to a file.
// renderedRoute can be modified and it or an equivelant should be returned.
// renderedRoute format:
// {
// route: String, // Where the output file will end up (relative to outputDir)
// originalRoute: String, // The route that was passed into the renderer, before redirects.
// html: String, // The rendered HTML for this route.
// outputPath: String // The path the rendered HTML will be written to.
// }
postProcess (renderedRoute) {
// Ignore any redirects.
renderedRoute.route = renderedRoute.originalRoute
// Basic whitespace removal. (Don't use this in production.)
renderedRoute.html = renderedRoute.html.split(/>[\s]+</gmi).join('><')
// Remove /index.html from the output path if the dir name ends with a .html file extension.
// For example: /dist/dir/special.html/index.html -> /dist/dir/special.html
if (renderedRoute.route.endsWith('.html')) {
renderedRoute.outputPath = path.join(__dirname, 'dist', renderedRoute.route)
}

return renderedRoute
},

// Optional - Uses html-minifier (https://github.com/kangax/html-minifier)
// To minify the resulting HTML.
// Option reference: https://github.com/kangax/html-minifier#options-quick-reference
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true
},

// Server configuration options.
server: {
// Normally a free port is autodetected, but feel free to set this if needed.
port: 8001
},

// The actual renderer to use. (Feel free to write your own)
// Available renderers: https://github.com/Tribex/prerenderer/tree/master/renderers
renderer: new Renderer({
// Optional - The name of the property to add to the window object with the contents of `inject`.
injectProperty: '__PRERENDER_INJECTED',
// Optional - Any values you'd like your app to have access to via `window.injectProperty`.
inject: {
foo: 'bar'
},

// Optional - defaults to 0, no limit.
// Routes are rendered asynchronously.
// Use this to limit the number of routes rendered in parallel.
maxConcurrentRoutes: 4,

// Optional - Wait to render until the specified event is dispatched on the document.
// eg, with `document.dispatchEvent(new Event('custom-render-trigger'))`
renderAfterDocumentEvent: 'custom-render-trigger',

// Optional - Wait to render until the specified element is detected using `document.querySelector`
renderAfterElementExists: 'my-app-element',

// Optional - Wait to render until a certain amount of time has passed.
// NOT RECOMMENDED
renderAfterTime: 5000, // Wait 5 seconds.

// Other puppeteer options.
// (See here: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions)
headless: false // Display the browser window when rendering. Useful for debugging.
})
})

renderAfterTime 这个属性最好配置,可以在等待一定时间后在来导出另一个路由文件,如果不添加,可能会出Unable to perrender all routes的错误。

上线到服务器

打包后可以先在本地的服务器上面测试,这里推荐一个npm包: serve 安装后通过 serve 文件夹名字启动一个本地服务。

需要注意的是:

  1. 当项目正常运行,同时包含多个路由的时候,当我们在除了首页以外的其他的目录刷新页面的时候都是404,这是因为服务器的配置问题。本地这里无法实现。

  2. 开发的时候必须使用 History 路由而不能使用 Hash 路由。

1, 2 文件的解决方法就是修改nginx的配置如下

1
2
3
4
5
6
7
location /{
index index.html index.htm;
if (!-e $request_filename) {
rewrite ^/(.*) /index.html last;
break;
}
}
  1. 对于动态路由,如/news/detail/:id是不支持的,推荐使用query路由,如/new/detail?id=
文章作者: 踏浪
文章链接: https://www.lyt007.cn/技术/react项目预渲染开发.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 踏浪 - 前端技术分享
支付宝
微信打赏