react項目預渲染開發

  • 2019 年 11 月 28 日
  • 筆記

react越來越火了,是開react開發的人員而是越來越多。但是因為單頁應用SEO的問題,我們也不得不去解決這個問題。不管是哪裡,都提供了兩種方案,一種是SSR服務端渲染,另一種則是預渲染方式。本篇文章主要是闡述預渲染的方案。

什麼是預渲染

在一般的react項目中(比如使用create-react-app創建的項目),我們在最後打包的時候只會生成一個HTML,JS與CSS文件,或許你會採用一些方法,比如公共文件拆分,路由懶載入等等生成多個文件,但是也無法從根本上解決這個問題,所有的資源還是通過JS動態的生成渲染的。

所以,所謂的預渲染就是在單頁應用中,將用戶交互不多,同時需要SEO的頁面單獨提取出來的一種方法,提取出來的就是一個HTML文件。

怎麼使用預渲染

開發react項目的時候,一般都是結合者webpack使用的。目前用的最多的預渲染的方法,就是使用webpack插件prerender-spa-plugin。這是一個webpack插件,所以使用直接在webpack的插件配置項中添加

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,如果你是自己搭建或者使用的是其他的方式,或許目錄名字會有所不同。

更多具體的配置

// 摘取自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的配置如下

location /{      index  index.html index.htm;      if (!-e $request_filename) {          rewrite ^/(.*) /index.html last;          break;      }  }
  1. 對於動態路由,如/news/detail/:id是不支援的,推薦使用query路由,如/new/detail?id=