详解超简单的react服务器渲染(ssr)入坑指南


Posted in Javascript onFebruary 28, 2019

前言

本文是基于react ssr的入门教程,在实际项目中使用还需要做更多的配置和优化,比较适合第一次尝试react ssr的小伙伴们。技术涉及到 koa2 + react,案例使用create-react-app创建

SSR 介绍

Server Slide Rendering,缩写为 ssr 即服务器端渲染,这个要从SEO说起,目前react单页应用HTML代码是下面这样的

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="utf-8" />
  <link rel="shortcut icon" href="favicon.ico" rel="external nofollow" />
  <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个问题。

React SSR介绍

这里通过一个例子来带大家入坑!先使用create-react-app创建一个react项目。因为要修改webpack,这里我们使用react-app-rewired启动项目。根目录创建一个server目录存放服务端代码,服务端代码我们这里使用koa2。目录结构如下:

详解超简单的react服务器渲染(ssr)入坑指南

这里先来看看react ssr是怎么工作的。

详解超简单的react服务器渲染(ssr)入坑指南

这个业务流程图比较清晰了,服务端只生成HTML代码,实际上前端会生成一份main.js提供给服务端的HTML使用。这就是react ssr的工作流程。有了这个图会更好的理解,如果这个业务没理解清楚,后面的估计很难理解。

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服务器渲染(ssr)入坑指南

create-react-app 的demo我没动过,直接用这个做案例了,前端项目基本上就没改了,等会儿我们服务器端要使用这个模块。代码如下:

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" rel="external nofollow" 
      target="_blank"
      rel="noopener noreferrer"
     >
      Learn React
     </a>
    </header>
   </div>
  );
 }
}

export default App;

在项目中新建server目录,用于存放服务端代码。为了简化,我这里只有2个文件,项目中我们用的ES6,所以还要配置下.babelrc

详解超简单的react服务器渲染(ssr)入坑指南

.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" rel="external nofollow" />
  <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" rel="external nofollow" />
  <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代码检查下:

详解超简单的react服务器渲染(ssr)入坑指南

详解超简单的react服务器渲染(ssr)入坑指南
没有{{root}}了,服务器渲染成功!

总结

相信这篇文章是最简单的react服务器渲染案例了,这里交出github地址https://github.com/mtsee/react-koa2-ssr

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript 写类方式之二
Jul 05 Javascript
js获取class的所有元素
Mar 28 Javascript
Node.js 应用跑得更快 10 个技巧
Apr 03 Javascript
jQuery qrcode生成二维码的方法
Apr 03 Javascript
Bootstrap CSS组件之按钮下拉菜单
Dec 17 Javascript
JS及JQuery对Html内容编码,Html转义
Feb 17 Javascript
js原生日历的实例(推荐)
Oct 31 Javascript
vue 表单之通过v-model绑定单选按钮radio
May 13 Javascript
Vue配置marked链接添加target=&quot;_blank&quot;的方法
Jul 19 Javascript
原生js基于canvas实现一个简单的前端截图工具代码实例
Sep 10 Javascript
vue 实现click同时传入事件对象和自定义参数
Jan 29 Vue.js
node.js如何自定义实现一个EventEmitter
Jul 16 Javascript
JS浅拷贝和深拷贝原理与实现方法分析
Feb 28 #Javascript
微信小程序搜索功能(附:小程序前端+PHP后端)
Feb 28 #Javascript
详解写好JS条件语句的5条守则
Feb 28 #Javascript
JS判断两个数组或对象是否相同的方法示例
Feb 28 #Javascript
jQuery.parseJSON()函数详解
Feb 28 #jQuery
js获取form表单中name属性的值
Feb 27 #Javascript
用VueJS写一个Chrome浏览器插件的实现方法
Feb 27 #Javascript
You might like
不要轻信 PHP_SELF的安全问题
2009/09/05 PHP
手把手教你打印出PDF(关于fpdf的简单应用)
2013/06/25 PHP
php实现将数组转换为XML的方法
2015/03/09 PHP
学习php设计模式 php实现访问者模式(Visitor)
2015/12/07 PHP
学习php设计模式 php实现原型模式(prototype)
2015/12/07 PHP
phpStudy中升级MySQL版本到5.7.17的方法步骤
2017/08/03 PHP
filters.revealTrans.Transition使用方法小结
2010/08/19 Javascript
基于Jquery实现的一个图片滚动切换
2012/06/21 Javascript
JS格式化数字金额用逗号隔开保留两位小数
2013/10/18 Javascript
Javascript实现滚动图片新闻的实例代码
2013/11/27 Javascript
js sort 二维数组排序的用法小结
2014/01/24 Javascript
JS运动相关知识点小结(附弹性运动示例)
2016/01/08 Javascript
js微信扫描二维码登录网站技术原理
2016/12/01 Javascript
jquery Easyui Datagrid实现批量操作(编辑,删除,添加)
2017/02/20 Javascript
jquery ui sortable拖拽后保存位置
2017/04/27 jQuery
[js高手之路]设计模式系列课程-发布者,订阅者重构购物车的实例
2017/08/29 Javascript
js如何获取图片url的Blob值并预览示例代码
2019/03/07 Javascript
js array数组对象操作方法汇总
2019/03/18 Javascript
微信小程序返回上一页传参并刷新过程解析
2019/12/13 Javascript
微信小程序onShareTimeline()实现分享朋友圈
2021/01/07 Javascript
[03:37]2014DOTA2国际邀请赛 主赛事第一日胜者组TOPPLAY
2014/07/19 DOTA
python写的ARP攻击代码实例
2014/06/04 Python
TensorFlow saver指定变量的存取
2018/03/10 Python
Python3基于sax解析xml操作示例
2018/05/22 Python
Numpy中矩阵matrix读取一列的方法及数组和矩阵的相互转换实例
2018/07/02 Python
Python中flatten( )函数及函数用法详解
2018/11/02 Python
python pyecharts 实现一个文件绘制多张图
2020/05/13 Python
Tensorflow实现将标签变为one-hot形式
2020/05/22 Python
Python如何使用input函数获取输入
2020/08/06 Python
HTML5拖拽文件到浏览器并实现文件上传下载功能代码
2013/06/06 HTML / CSS
红色康乃馨酒店:Red Carnation Hotels
2017/06/22 全球购物
优秀毕业生推荐信
2013/11/02 职场文书
传播学专业毕业生自荐信
2013/11/04 职场文书
秋季开学典礼主持词
2014/03/19 职场文书
Python如何把不同类型数据的json序列化
2021/04/30 Python
MySQL的InnoDB存储引擎的数据页结构详解
2022/03/03 MySQL