超簡單的react服務器渲染(ssr)入坑指南

前言

本文是基於react ssr的入門教程,在實際項目中使用還須要作更多的配置和優化,比較適合第一次嘗試react ssr的小夥伴們。技術涉及到 koa2 + react,案例使用create-react-app建立javascript

SSR 介紹

Server Slide Rendering,縮寫爲 ssr 即服務器端渲染,這個要從SEO提及,目前react單頁應用HTML代碼是下面這樣的css

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <meta name="theme-color" content="#000000" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script src="/js/main.js"></script>
  </body>
</html>
  1. 若是main.js 加載比較慢,會出現白屏一閃的現象。
  2. 傳統的搜索引擎爬蟲由於不能抓取JS生成後的內容,遇到單頁web項目,抓取到的內容啥也沒有。在SEO上會吃不少虧,很難排搜索引擎到前面去。

React SSR(react服務器渲染)正好解決了這2個問題。html

React SSR介紹

這裏經過一個例子來帶你們入坑!先使用create-react-app建立一個react項目。由於要修改webpack,這裏咱們使用react-app-rewired啓動項目。根目錄建立一個server目錄存放服務端代碼,服務端代碼咱們這裏使用koa2。目錄結構以下:
圖片描述前端

這裏先來看看react ssr是怎麼工做的。java

圖片描述

這個業務流程圖比較清晰了,服務端只生成HTML代碼,實際上前端會生成一份main.js提供給服務端的HTML使用。這就是react ssr的工做流程。有了這個圖會更好的理解,若是這個業務沒理解清楚,後面的估計很難理解。node

react提供的SSR方法有兩個renderToString 和 renderToStaticMarkup,區別以下:
  • renderToString 方法渲染的時候帶有 data-reactid 屬性. 在瀏覽器訪問頁面的時候,main.js能識別到HTML的內容,不會執行React.createElement二次建立DOM。
  • renderToStaticMarkup 則沒有 data-reactid 屬性,頁面看上去幹淨點。在瀏覽器訪問頁面的時候,main.js不能識別到HTML內容,會執行main.js裏面的React.createElement方法從新建立DOM。

實現流程

好了,咱們都知道原理了,能夠開始coding了,目錄結構以下:
圖片描述react

create-react-app 的demo我沒動過,直接用這個作案例了,前端項目基本上就沒改了,等會兒咱們服務器端要使用這個模塊。代碼以下:webpack

import "./App.css";

import React, { Component } from "react";

import logo from "./logo.svg";

class App extends Component {
  componentDidMount() {
    console.log('哈哈哈~ 服務器渲染成功了!');
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}

export default App;

在項目中新建server目錄,用於存放服務端代碼。爲了簡化,我這裏只有2個文件,項目中咱們用的ES6,因此還要配置下.babelrc
圖片描述git

.babelrc 配置,由於要使用到ES6
{
    "presets": [
        "env",
        "react"
    ],
    "plugins": [
        "transform-decorators-legacy",
        "transform-runtime",
        "react-hot-loader/babel",
        "add-module-exports",
        "transform-object-rest-spread",
        "transform-class-properties",
        [
            "import",
            {
                "libraryName": "antd",
                "style": true
            }
        ]
    ]
}
index.js 項目入口作一些預處理,使用asset-require-hook過濾掉一些相似 import logo from "./logo.svg"; 這樣的資源代碼。由於咱們服務端只須要純的HTML代碼,不過濾掉會報錯。這裏的name,咱們是去掉了hash值的
require("asset-require-hook")({
  extensions: ["svg", "css", "less", "jpg", "png", "gif"],
  name: '/static/media/[name].[ext]'
});
require("babel-core/register")();
require("babel-polyfill");
require("./app");
public/index.html html模版代碼要作個調整, {{root}} 這個能夠是任何能夠替換的字符串,等下服務端會替換這段字符串。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <meta name="theme-color" content="#000000" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root">{{root}}</div>
  </body>
</html>
app.js 服務端渲染的主要代碼,加載App.js,使用renderToString 生成html代碼,去替換掉 index.html 中的 {{root}} 部分
import App from '../src/App';
import Koa from 'koa';
import React from 'react';
import Router from 'koa-router';
import fs from 'fs';
import koaStatic from 'koa-static';
import path from 'path';
import { renderToString } from 'react-dom/server';

// 配置文件
const config = {
  port: 3030
};

// 實例化 koa
const app = new Koa();

// 靜態資源
app.use(
  koaStatic(path.join(__dirname, '../build'), {
    maxage: 365 * 24 * 60 * 1000,
    index: 'root' 
    // 這裏配置不要寫成'index'就能夠了,由於在訪問localhost:3030時,不能讓服務默認去加載index.html文件,這裏很容易掉進坑。
  })
);

// 設置路由
app.use(
  new Router()
    .get('*', async (ctx, next) => {
      ctx.response.type = 'html'; //指定content type
      let shtml = '';
      await new Promise((resolve, reject) => {
        fs.readFile(path.join(__dirname, '../build/index.html'), 'utfa8', function(err, data) {
          if (err) {
            reject();
            return console.log(err);
          }
          shtml = data;
          resolve();
        });
      });
      // 替換掉 {{root}} 爲咱們生成後的HTML
      ctx.response.body = shtml.replace('{{root}}', renderToString(<App />));
    })
    .routes()
);

app.listen(config.port, function() {
  console.log('服務器啓動,監聽 port: ' + config.port + '  running~');
});
config-overrides.js 由於咱們用的是create-react-app,這裏使用react-app-rewired去改下webpack的配置。由於執行 npm run build的時候會自動給資源加了hash值,而這個hash值,咱們在asset-require-hook的時候去掉了hash值,配置裏面須要改下,否則會出現圖片不顯示的問題,這裏也是一個坑,要注意下。
module.exports = {
  webpack: function(config, env) {
    // ...add your webpack config
    // console.log(JSON.stringify(config));
    // 去掉hash值,解決asset-require-hook資源問題
    config.module.rules.forEach(d => {
      d.oneOf &&
        d.oneOf.forEach(e => {
          if (e && e.options && e.options.name) {
            e.options.name = e.options.name.replace('[hash:8].', '');
          }
        });
    });
    return config;
  }
};

好了,全部的代碼就這些了,是否是很簡單了?咱們koa2讀取的靜態資源是 build目錄下面的。先執行npm run build打包項目,再執行node ./server 啓動服務端項目。看下http://localhost:3030頁面的HTML代碼檢查下:
圖片描述
圖片描述
沒有{{root}}了,服務器渲染成功!github

總結

相信這篇文章是最簡單的react服務器渲染案例了,這裏交出github地址,若是學會了,記得給個star

https://github.com/mtsee/reac...
相關文章
相關標籤/搜索