react 服务器端渲染

In the previous article, we described how to make a production build and deploy it to a server. Naturally, the next step is the server-side rendering. We are going to walk through the process by converting Create React App to a server-side rendered application.

在上一篇文章中,我们描述了如何进行生产构建并将其部署到服务器。 自然,下一步就是服务器端渲染。 我们将通过将Create React App转换为服务器端渲染的应用程序来完成整个过程。

术语 (Terminologies)

什么是客户端渲染(CSR)?(What is client-side rendering (CSR)?)

It is a technology that a browser downloads the minimal HTML page, which uses JavaScript to render and fills the content.

这是一种浏览器下载最小HTML页面的技术,该页面使用JavaScript呈现和填充内容。

CSR may take longer for the initial page loading, but the subsequent loading would be faster. It off-loads the server and relies on the power of JavaScript libraries. However, it is hard for Search Engine Optimization (SEO) as there is no static content to be crawled upon.

CSR可能需要更长的时间来进行初始页面加载,但是随后的加载会更快。 它减轻了服务器的负担,并依靠JavaScript库的强大功能。 但是,由于没有要爬网的静态内容,因此搜索引擎优化(SEO)很难。

什么是服务器端渲染(SSR)? (What is server-side rendering (SSR)?)

It is a technology that a browser downloads the complete HTML page, which has been rendered by the server.

这是一种浏览器下载由服务器呈现的完整HTML页面的技术。

The advantage of SSR is for SEO. The initial page loading is faster. But it needs the full page reloading for the subsequent changes. This may overload the server.

SSR的优势在于SEO。 初始页面加载速度更快。 但是它需要重新加载整个页面才能进行后续更改。 这可能会使服务器超载。

什么是单页应用程序(SPA)? (What is single-page application (SPA)?)

It is a an application that uses the client-side rendering. Instead of having a different HTML page per route, it renders each route dynamically and directly in the browser.

它是使用客户端呈现的应用程序。 它没有在每个路由上显示不同HTML页面,而是在浏览器中直接动态地呈现每个路由。

什么是通用(同构)JavaScript? (What is universal (isomorphic) JavaScript?)

It is a Javascript application that runs on both the client and the server. It renders HTML on the client as SPA, and it also renders the same HTML on the server side and then sends it to the browser to display.

它是一个可在客户端和服务器上运行的Javascript应用程序。 它在客户端上将HTML呈现为SPA,还在服务器端上呈现相同HTML,然后将其发送到浏览器进行显示。

We write React code for CSR. The same code base can be used for SSR. React is universal JavaScript.

我们为CSR编写React代码。 相同的代码库可用于SSR。 React是通用JavaScript。

SSR exists before CSR. Today, it is revived with universal JavaScript. When SSR is mentioned today, it likely means SSR with universal JavaScript.

SSR在CSR之前存在。 如今,它已通过通用JavaScript复活。 今天提到SSR时,它可能意味着具有通用JavaScript的SSR。

创建React App和CSR (Create React App and CSR)

Install Create React App, and run npm start.

安装Create React App ,然后运行npm start

From the Elements tab, it shows the JavaScript rendered HTML (JSX) for the spinning logo and some text information.

在“ Elements选项卡中,它显示了旋转徽标JavaScript呈现HTML(JSX)和一些文本信息。

This is a typical CSR, where HTML content is rendered by JavaScript. From the Network tab, we can read what is downloaded from the server.

这是典型的CSR,其中HTML内容由JavaScript呈现。 从“ Network选项卡中,我们可以读取从服务器下载的内容。

The HTML’s body has a bunch of JavaScript files, but no actual content. It is hard for SEO to get any meaningful information.

HTML的主体中有一堆JavaScript文件,但没有实际内容。 SEO很难获得任何有意义的信息。

<body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><!--This HTML file is a template.If you open it directly in the browser, you will see an empty page.You can add webfonts, meta tags, or analytics to this file.The build step will place the bundled scripts into the <body> tag.To begin the development, run `npm start` or `yarn start`.To create a production bundle, use `npm run build` or `yarn build`.--><script src="/static/js/bundle.js"></script><script src="/static/js/0.chunk.js"></script><script src="/static/js/main.chunk.js"></script>
</body>

使用Express部署生产版本(Deploy the Production Build With Express)

A hands-on guide for creating a production-ready React app sets up the foundation work for server-side rendering.

创建生产就绪型React应用程序的动手指南为服务器端渲染奠定了基础。

Here is a brief recap.

这是一个简短的回顾。

Create server/index.js as follows:

如下创建server/index.js

const express = require("express");
const path = require("path");
const app = express();
app.use(express.static(path.join(__dirname, "../build")));app.get("/*", (req, res) => {res.sendFile(path.join(__dirname, "../build", "index.html"));
});app.listen(8080, () =>console.log("Express server is running on localhost:8080")
);

Execute npm run build to create a production build. Then run nodemon server to deploy it with the Express server.

执行npm run build创建生产版本。 然后运行nodemon server以将其与Express服务器一起部署。

From the Network tab, it shows what is retrieved from the server:

在“ Network选项卡中,它显示了从服务器检索的内容:

<!doctype html>
<html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><link href="/static/css/main.5f361e03.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e) {function r(r) {for (var n, a, p = r[0], l = r[1], f = r[2], c = 0, s = []; c < p.length; c++)a = p[c],Object.prototype.hasOwnProperty.call(o, a) && o[a] && s.push(o[a][0]),o[a] = 0;for (n in l)Object.prototype.hasOwnProperty.call(l, n) && (e[n] = l[n]);for (i && i(r); s.length; )s.shift()();return u.push.apply(u, f || []),t()}function t() {for (var e, r = 0; r < u.length; r++) {for (var t = u[r], n = !0, p = 1; p < t.length; p++) {var l = t[p];0 !== o[l] && (n = !1)}n && (u.splice(r--, 1),e = a(a.s = t[0]))}return e}var n = {}, o = {1: 0}, u = [];function a(r) {if (n[r])return n[r].exports;var t = n[r] = {i: r,l: !1,exports: {}};return e[r].call(t.exports, t, t.exports, a),t.l = !0,t.exports}a.m = e,a.c = n,a.d = function(e, r, t) {a.o(e, r) || Object.defineProperty(e, r, {enumerable: !0,get: t})},a.r = function(e) {"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {value: "Module"}),Object.defineProperty(e, "__esModule", {value: !0})},a.t = function(e, r) {if (1 & r && (e = a(e)),8 & r)return e;if (4 & r && "object" == typeof e && e && e.__esModule)return e;var t = Object.create(null);if (a.r(t),Object.defineProperty(t, "default", {enumerable: !0,value: e}),2 & r && "string" != typeof e)for (var n in e)a.d(t, n, function(r) {return e[r]}.bind(null, n));return t},a.n = function(e) {var r = e && e.__esModule ? function() {return e.default}: function() {return e};return a.d(r, "a", r),r},a.o = function(e, r) {return Object.prototype.hasOwnProperty.call(e, r)},a.p = "/";var p = this["webpackJsonpreact-app5"] = this["webpackJsonpreact-app5"] || [], l = p.push.bind(p);p.push = r,p = p.slice();for (var f = 0; f < p.length; f++)r(p[f]);var i = l;t()}([])</script><script src="/static/js/2.97694a1e.chunk.js"></script><script src="/static/js/main.c9a6c259.chunk.js"></script></body>
</html>

There are 3 JavaScript files (lines 17 - 124, line 125, line 126) with the empty markup content (line 16). Hence, it is CSR.

有3个JavaScript文件(第17-124行,第125行,第126行)的标记内容为空(第16行)。 因此,这就是企业社会责任。

在Express Server内部构建SSR (Build SSR Inside the Express Server)

There are 3 steps to build SSR inside the Express server.

在Express服务器中构建SSR包括3个步骤。

步骤1:使用ReactDOM.hydrate()来显示服务器渲染的标记。 (Step 1: Use ReactDOM.hydrate() to display the server-rendered markup.)

ReactDOM.hydrate() is similar to as ReactDOM.render(). It is used to hydrate a container whose HTML contents have been rendered by the ReactDOMServer object. Its syntax is ReactDOM.hydrate(element, container[, callback]), almost identical to ReactDOM.render(element, container[, callback]).

ReactDOM.hydrate()ReactDOM.render()类似。 它用于水合其HTML内容已由ReactDOMServer对象呈现的ReactDOMServer 。 它的语法是ReactDOM.hydrate(element, container[, callback]) ,几乎与ReactDOM.render(element, container[, callback])

Since ReactDOM.hydrate() is called on a node that already has the server-rendered markup, React will preserve it and only attach event handlers. This makes the initial load performant.

由于在已经具有服务器渲染标记的节点上调用了ReactDOM.hydrate() ,因此React将保留它并仅附加事件处理程序。 这使初始负载表现出色。

ReactDOM.hydrate() is used in src/index.js:

ReactDOM.hydrate()用于src/index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";ReactDOM.hydrate(<React.StrictMode><App /></React.StrictMode>,document.getElementById("root")
);// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

At line 7, ReactDOM.render() is replaced by ReactDOM.hydrate().

在第7行, ReactDOM.render()被替换ReactDOM.hydrate()

步骤2:使用ReactDOMServer对象将组件呈现为静态标记。 (Step 2: Use ReactDOMServer object to render components to static markup.)

React provides the ReactDOMServer object to render components to static markup. It sends to the browser a page that has been populated with data.

React提供了ReactDOMServer对象来将组件呈现为静态标记。 它将向浏览器发送一个已填充数据的页面。

React code is universal JavaScript, which runs on both the client and the server.

React代码是通用JavaScript,可在客户端和服务器上运行。

There are different packages and approaches to achieve SSR. @babel/register is one of the packages. It is installed as part of devDependencies, along with babel-plugin-transform-assets.

有不同的软件包和方法来实现SSR。 @babel/register是软件包之一。 它与babel-plugin-transform-assets一起作为devDependencies一部分安装。

 "devDependencies": {  "@babel/register": "^7.11.5",  "babel-plugin-transform-assets": "^1.0.2"}

This is SSR implementation in server/index.js:

这是server/index.js SSR实现:

require("@babel/register")({presets: ["@babel/preset-env", "@babel/preset-react"],"plugins": [["transform-assets",{"extensions": ["css","svg"],"name": "static/media/[name].[hash:8].[ext]"}]]
});
const React = require("react");
const ReactDOMServer = require("react-dom/server");
const App = require("../src/App").default;
const express = require("express");
const path = require("path");
const fs = require("fs");const app = express();app.get("/*", (req, res, next) => {console.log(`Request URL = ${req.url}`);if (req.url !== '/') {return next();}const reactApp = ReactDOMServer.renderToString(React.createElement(App));console.log(reactApp);const indexFile = path.resolve("build/index.html");fs.readFile(indexFile, "utf8", (err, data) => {if (err) {const errMsg = `There is an error: ${err}`;console.error(errMsg);return res.status(500).send(errMsg);}return res.send(data.replace('<div id="root"></div>', `<div id="root">${reactApp}</div>`));});
});app.use(express.static(path.resolve(__dirname, "../build")));app.listen(8080, () =>console.log("Express server is running on localhost:8080")
);

At line 1, we set up Babel through the require hook, which automatically compiles files on the fly. Without this hook and the associated presets, we will encounter SyntaxError: Cannot use import statement outside a module.

在第1行,我们通过require钩子设置了Babel,该钩子会自动自动编译文件。 没有此挂钩和关联的预设,我们将遇到SyntaxError: Cannot use import statement outside a module

 $ nodemon server[nodemon] 2.0.4[nodemon] to restart at any time, enter `rs`[nodemon] watching path(s): *.*[nodemon] watching extensions: js,mjs,json[nodemon] starting `node server`/Users/fuje/app/react-app1/src/App.js:1import React from "react";^^^^^^ SyntaxError: Cannot use import statement outside a module    at wrapSafe (internal/modules/cjs/loader.js:1071:16)    at Module._compile (internal/modules/cjs/loader.js:1121:27)    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1177:10)    at Module.load (internal/modules/cjs/loader.js:1001:32)    at Function.Module._load (internal/modules/cjs/loader.js:900:14)    at Module.require (internal/modules/cjs/loader.js:1043:19)    at require (internal/modules/cjs/helpers.js:77:18)    at Object.<anonymous> (/Users/fuje/app/react-app1/server/index.js:3:13)    at Module._compile (internal/modules/cjs/loader.js:1157:30)    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1177:10)[nodemon] app crashed - waiting for file changes before starting...

At line 2, we set up two presets:

在第2行,我们设置了两个预设:

  • @babel/preset-env, a smart preset that uses the latest JavaScript without needing to micromanage which syntax transforms are needed for the target environments.

    @babel/preset-env ,这是一个智能预设,它使用最新JavaScript,而无需对目标环境进行必要的语法转换进行微观管理。

  • @babel/preset-react, a smart preset that automatically imports the functions that JSX transpiles to.

    @babel/preset-react ,一个智能预设,可自动导入JSX转换为的功能。

Lines 3 - 14 are plugins for babel-plugin-transform-assets. It sets up rules on how to transform static media files. Without this, it throws SyntaxError: Unexpected token ‘<’ for the svg tag.

第3至14行是babel-plugin-transform-assets 。 它设置了有关如何转换静态媒体文件的规则。 如果没有此设置,它将为svg标记引发SyntaxError: Unexpected token '<'

 $ nodemon server[nodemon] 2.0.4[nodemon] to restart at any time, enter `rs`[nodemon] watching path(s): *.*[nodemon] watching extensions: js,mjs,json[nodemon] starting `node server`/Users/fuje/app/react-app5/src/logo.svg:1<svg xmlns=" http://www.w3.org/2000/svg " viewBox="0 0 841.9 595.3">^ SyntaxError: Unexpected token '<'    at wrapSafe (internal/modules/cjs/loader.js:1071:16)    at Module._compile (internal/modules/cjs/loader.js:1121:27)    at Module._extensions..js (internal/modules/cjs/loader.js:1177:10)    at Object.newLoader [as .js] (/Users/fuje/app/react-app5/node_modules/pirates/lib/index.js:104:7)    at Module.load (internal/modules/cjs/loader.js:1001:32)    at Function.Module._load (internal/modules/cjs/loader.js:900:14)    at Module.require (internal/modules/cjs/loader.js:1043:19)    at require (internal/modules/cjs/helpers.js:77:18)    at Object.<anonymous> (/Users/fuje/app/react-app5/src/App.js:2:1)    at Module._compile (internal/modules/cjs/loader.js:1157:30)[nodemon] app crashed - waiting for file changes before starting...

At line 16, React is required.

在第16行,需要React。

At line 17, ReactDOMServer is required.

在第17行,需要ReactDOMServer

At line 18, the default export of src/App.js is required.

在第18行,需要默认导出src/App.js

We move app.use() to line 47, after app.get() (lines 25 - 45). Otherwise, app.use() will serve the static files, including index.html, for the root route, and the execution will not have a chance to reach the app.get() middleware.

我们移动app.use()到第47行,之后app.get()行25 - 45)。 否则, app.use()将为根路由提供静态文件,包括index.html ,并且执行将没有机会到达app.get()中间件。

Line 26 displays the request URL that is invoked. For Create React App, they are listed as follows:

第26行显示了被调用的请求URL。 对于Create React App,它们列出如下:

 Request URL = /Request URL = /static/css/main.519b5a55.chunk.cssRequest URL = /static/media/logo.5d5d9eef.svgRequest URL = /static/js/main.fdf902fb.chunk.jsRequest URL = /static/js/2.bc7ff9af.chunk.jsRequest URL = /static/css/main.519b5a55.chunk.css.mapRequest URL = /static/js/main.fdf902fb.chunk.js.mapRequest URL = /static/js/2.bc7ff9af.chunk.js.mapRequest URL = /manifest.jsonRequest URL = /logo192.png

Lines 27 - 29 ensures only the root route is rendered by app.get(). The static assets will be skipped to the next middleware, which is at line 47.

第27-29行确保app.get()仅呈现根路由。 静态资产将被跳过到下一个中​​间件,即第47行。

At line 30, ReactDOMServer.renderToString(element) is used to generate HTML on the server. From the theory, it could be written as ReactDOMServer.renderToString(<App />). But that would throwSyntaxError: Unexpected token ‘<’.

在第30行, ReactDOMServer.renderToString(element)用于在服务器上生成HTML。 从理论上讲,它可以写为ReactDOMServer.renderToString(<App />) 。 但这会引发SyntaxError: Unexpected token '<'

 $ nodemon server[nodemon] 2.0.4[nodemon] to restart at any time, enter `rs`[nodemon] watching path(s): *.*[nodemon] watching extensions: js,mjs,json[nodemon] starting `node server`/Users/fuje/app/react-app5/server/index.js:42  const reactApp = ReactDOMServer.renderToString(<App />);                                                 ^ SyntaxError: Unexpected token '<'    at wrapSafe (internal/modules/cjs/loader.js:1071:16)    at Module._compile (internal/modules/cjs/loader.js:1121:27)    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1177:10)    at Module.load (internal/modules/cjs/loader.js:1001:32)    at Function.Module._load (internal/modules/cjs/loader.js:900:14)    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)    at internal/main/run_main_module.js:18:47[nodemon] app crashed - waiting for file changes before starting...

Requiring @babel-register will not work for the file where it is required, but it will work for files that are required afterwards. Either move the code including ReactDOMServer.renderToString(<App />) to a separate file to be required, or simply use React.createElement().

要求@babel-register不适用于所需的文件,但适用于此后所需的文件。 将包含ReactDOMServer.renderToString(<App />)的代码ReactDOMServer.renderToString(<App />)需要的单独文件中,或者简单地使用React.createElement()

Line 31 displays the server rendered markup code:

第31行显示服务器渲染的标记代码:

 <div class="App" data-reactroot=""><header class="App-header"><img src="static/media/logo.5d5d9eef.svg" class="App-logo" alt="logo"/><p>Edit <code>src/App.js</code> and save to reload.</p><a class="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">Learn React</a></header></div>

Line 33 loads the production index.html.

第33行加载了生产index.html

Lines 34 - 44 read the content of index.html. If there is no error, the server generated markup (line 42) is rendered to the root tag, and then the final index.html is responded to the initial loading.

第34-44行读取index.html的内容。 如果没有错误,则将服务器生成的标记(第42行)呈现给根标记,然后将最终的index.html响应到初始加载。

Execute nodemon server. From the Network tab, the downloaded script shows a server rendered markup.

执行nodemon server 。 从“ Network选项卡中,下载的脚本显示服务器渲染的标记。

<!doctype html>
<html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><link href="/static/css/main.5f361e03.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div class="App" data-reactroot=""><header class="App-header"><img src="static/media/logo.5d5d9eef.svg" class="App-logo" alt="logo"/><p>Edit <code>src/App.js</code>and save to reload.</p><a class="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">Learn React</a></header></div></div><script>!function(e) {function r(r) {for (var n, a, p = r[0], l = r[1], f = r[2], c = 0, s = []; c < p.length; c++)a = p[c],Object.prototype.hasOwnProperty.call(o, a) && o[a] && s.push(o[a][0]),o[a] = 0;for (n in l)Object.prototype.hasOwnProperty.call(l, n) && (e[n] = l[n]);for (i && i(r); s.length; )s.shift()();return u.push.apply(u, f || []),t()}function t() {for (var e, r = 0; r < u.length; r++) {for (var t = u[r], n = !0, p = 1; p < t.length; p++) {var l = t[p];0 !== o[l] && (n = !1)}n && (u.splice(r--, 1),e = a(a.s = t[0]))}return e}var n = {}, o = {1: 0}, u = [];function a(r) {if (n[r])return n[r].exports;var t = n[r] = {i: r,l: !1,exports: {}};return e[r].call(t.exports, t, t.exports, a),t.l = !0,t.exports}a.m = e,a.c = n,a.d = function(e, r, t) {a.o(e, r) || Object.defineProperty(e, r, {enumerable: !0,get: t})},a.r = function(e) {"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {value: "Module"}),Object.defineProperty(e, "__esModule", {value: !0})},a.t = function(e, r) {if (1 & r && (e = a(e)),8 & r)return e;if (4 & r && "object" == typeof e && e && e.__esModule)return e;var t = Object.create(null);if (a.r(t),Object.defineProperty(t, "default", {enumerable: !0,value: e}),2 & r && "string" != typeof e)for (var n in e)a.d(t, n, function(r) {return e[r]}.bind(null, n));return t},a.n = function(e) {var r = e && e.__esModule ? function() {return e.default}: function() {return e};return a.d(r, "a", r),r},a.o = function(e, r) {return Object.prototype.hasOwnProperty.call(e, r)},a.p = "/";var p = this["webpackJsonpreact-app5"] = this["webpackJsonpreact-app5"] || [], l = p.push.bind(p);p.push = r,p = p.slice();for (var f = 0; f < p.length; f++)r(p[f]);var i = l;t()}([])</script><script src="/static/js/2.bc7ff9af.chunk.js"></script><script src="/static/js/main.47e3d1e1.chunk.js"></script></body>
</html>

From the Elements tab, the following is the new body of the HTML:

在“ Elements选项卡中,以下是HTML的新正文:

<body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div class="App" data-reactroot=""><header class="App-header"><img src="static/media/logo.5d5d9eef.svg" class="App-logo" alt="logo"><p>Edit <code>src/App.js</code> and save to reload.</p><a class="App-link" href="https://reactjs.org"target="_blank" rel="noopener noreferrer">Learn React</a></header></div></div><script>!function (e) { function r(r) { for (var n, a, p = r[0], l = r[1], f = r[2], c = 0, s = []; c < p.length; c++)a = p[c], Object.prototype.hasOwnProperty.call(o, a) && o[a] && s.push(o[a][0]), o[a] = 0; for (n in l) Object.prototype.hasOwnProperty.call(l, n) && (e[n] = l[n]); for (i && i(r); s.length;)s.shift()(); return u.push.apply(u, f || []), t() } function t() { for (var e, r = 0; r < u.length; r++) { for (var t = u[r], n = !0, p = 1; p < t.length; p++) { var l = t[p]; 0 !== o[l] && (n = !1) } n && (u.splice(r--, 1), e = a(a.s = t[0])) } return e } var n = {}, o = { 1: 0 }, u = []; function a(r) { if (n[r]) return n[r].exports; var t = n[r] = { i: r, l: !1, exports: {} }; return e[r].call(t.exports, t, t.exports, a), t.l = !0, t.exports } a.m = e, a.c = n, a.d = function (e, r, t) { a.o(e, r) || Object.defineProperty(e, r, { enumerable: !0, get: t }) }, a.r = function (e) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e, "__esModule", { value: !0 }) }, a.t = function (e, r) { if (1 & r && (e = a(e)), 8 & r) return e; if (4 & r && "object" == typeof e && e && e.__esModule) return e; var t = Object.create(null); if (a.r(t), Object.defineProperty(t, "default", { enumerable: !0, value: e }), 2 & r && "string" != typeof e) for (var n in e) a.d(t, n, function (r) { return e[r] }.bind(null, n)); return t }, a.n = function (e) { var r = e && e.__esModule ? function () { return e.default } : function () { return e }; return a.d(r, "a", r), r }, a.o = function (e, r) { return Object.prototype.hasOwnProperty.call(e, r) }, a.p = "/"; var p = this["webpackJsonpreact-app5"] = this["webpackJsonpreact-app5"] || [], l = p.push.bind(p); p.push = r, p = p.slice(); for (var f = 0; f < p.length; f++)r(p[f]); var i = l; t() }([])</script><script src="/static/js/2.bc7ff9af.chunk.js"></script><script src="/static/js/main.47e3d1e1.chunk.js"></script>
</body>

The body content has the full content, which can be used by SEO to get meaningful information.

正文内容具有完整的内容,SEO可以使用这些内容来获取有意义的信息。

Although there are a bunch of JavaScript files, they are not executed. This can be proved by turning off JavaScript from the bowser.

尽管有一堆JavaScript文件,但它们不会执行。 可以通过关闭Bowser中JavaScript来证明这一点。

This is SSR. Disabling JavaScript, the code continues working.

这是SSR。 禁用JavaScript,代码将继续工作。

步骤3:处理页面的特定要求 (Step 3: Handle page specific requirements)

We made one-page React app work. How about an app with multiple routes?

我们完成了一页的React应用程序的工作。 有多个路由的应用程序怎么样?

First, install react-router-dom as one of dependencies.

首先,安装react-router-dom作为dependencies

 "devDependencies": {  “react-router-dom”: “^5.2.0”,  ...}

Modify src/App.js as follows:

修改src/App.js如下:

import React from "react";
import { BrowserRouter, Route, Link } from "react-router-dom";const Home = () => (<><h1>Home</h1><ul><li key="1"><Link to="/first-page">First Page</Link></li><li key="2"><Link to="/second-page">Second Page</Link></li></ul></>
);const FirstPage = () => (<><h1>First Page</h1><Link to="/">Go back Home</Link></>
);const SecondPage = () => (<><h1>Second Page</h1><Link to="/">Go back Home</Link></>
);function App() {console.log(process.env.NODE_ENV);return (<BrowserRouter><Route exact path="/" component={Home} /><Route path="/first-page" component={FirstPage} /><Route path="/second-page" component={SecondPage} /></BrowserRouter>);
}export default App;

Execute npm run build, and then run nodemon server.

执行npm run build ,然后运行nodemon server

 Error: Invariant failed: Browser history needs a DOM    at invariant (/Users/fuje/app/react-app5/node_modules/tiny-invariant/dist/tiny-invariant.cjs.js:13:11)    at Object.createHistory [as createBrowserHistory] (/Users/fuje/app/react-app5/node_modules/history/cjs/history.js:273:16)    at new BrowserRouter (/Users/fuje/app/react-app5/node_modules/react-router-dom/modules/BrowserRouter.js:11:13)    at processChild (/Users/fuje/app/react-app5/node_modules/react-dom/cjs/react-dom-server.node.development.js:2995:14)    at resolve (/Users/fuje/app/react-app5/node_modules/react-dom/cjs/react-dom-server.node.development.js:2960:5)    at ReactDOMServerRenderer.render (/Users/fuje/app/react-app5/node_modules/react-dom/cjs/react-dom-server.node.development.js:3435:22)    at ReactDOMServerRenderer.read (/Users/fuje/app/react-app5/node_modules/react-dom/cjs/react-dom-server.node.development.js:3373:29)    at Object.renderToString (/Users/fuje/app/react-app5/node_modules/react-dom/cjs/react-dom-server.node.development.js:3988:27)    at /Users/fuje/app/react-app5/server/index.js:42:35    at Layer.handle [as handle_request] (/Users/fuje/app/react-app5/node_modules/express/lib/router/layer.js:95:5)

Unfortunately, BrowserRoute uses the HTML5 pushState history API under the hood, which is not supported by Node,js.

不幸的是, BrowserRouteBrowserRoute使用了HTML5 pushState历史记录API , BrowserRoute不支持。

For SSR, StaticRouter should be used in universal JavaScript. However, StaticRouter is currently an alpha software.

对于SSR,应在通用JavaScript中使用StaticRouter 。 但是, StaticRouter当前是Alpha软件。

We are developing a package to work with static route configs and React Router, to continue to meet those use-cases. It is under development now but we’d love for you to try it out and help out.

我们正在开发一个用于静态路由配置和React Router的软件包,以继续满足这些用例。 现在正在开发中,但我们希望您能尝试一下并提供帮助。

React Router Config

React路由器配置

Another choice is MemoryRouter, which keeps the URL history in memory (does not read or write to the address bar). It is useful in tests and non-browser environments.

另一个选择是MemoryRouter,它将URL历史记录保留在内存中(不读取或写入地址栏)。 它在测试和非浏览器环境中很有用。

Here is the src/App.js:

这是src/App.js

import React from "react";
import { MemoryRouter, Route, Link } from "react-router-dom";const Home = () => (<><h1>Home</h1><ul><li key="1"><Link to="/first-page">First Page</Link></li><li key="2"><Link to="/second-page">Second Page</Link></li></ul></>
);const FirstPage = () => (<><h1>First Page</h1><Link to="/">Go back Home</Link></>
);const SecondPage = () => (<><h1>Second Page</h1><Link to="/">Go back Home</Link></>
);function App() {console.log(process.env.NODE_ENV);return (<MemoryRouter><Route exact path="/" component={Home} /><Route path="/first-page" component={FirstPage} /><Route path="/second-page" component={SecondPage} /></MemoryRouter>);
}export default App;

Line 35 and line 39 use MemoryRouter.

第35行和第39行使用MemoryRouter

Execute npm run build, and then run nodemon server. Go to http://localhost:8080, we see the following page:

执行npm run build ,然后运行nodemon server 。 转到http://localhost:8080 ,我们看到以下页面:

The app works, although URL in the address bar will not update.

该应用程序可以运行,尽管地址栏中的URL不会更新。

Routing works with a caveat. There are also other things to be taken care of, such as data fetching, Redux, etc. The work at the server side is not as straightforward as the client side. Each page may need specific care based on its requirement.

路由需要注意。 还有其他需要注意的事情,例如数据获取,Redux等。服务器端的工作不像客户端那样简单。 每页可能需要根据其要求进行特殊护理。

结论 (Conclusion)

We have showed how to set up SSR for Create React App. These are the steps:

我们已经展示了如何为Create React App设置SSR。 这些步骤是:

  1. Use ReactDOM.hydrate() to display the server-rendered markup.

    使用ReactDOM.hydrate()来显示服务器渲染的标记。

  2. Use ReactDOMServer object to render components to static markup.

    使用ReactDOMServer对象将组件呈现为静态标记。

  3. Handle page specific requirements处理页面的特定要求

We use ReactDOMServer object to render components to static markup. It can be used in SSR and static rendering.

我们使用ReactDOMServer对象将组件呈现为静态标记。 它可以用于SSR和静态渲染。

SSR happens on-demand when a file is requested. A static rendering happens once at build time. Both of them are SEO friendly.

请求文件时,SSR按需发生。 静态渲染在构建时发生一次。 他们俩都是SEO友好的。

If the page includes only static data, the static rendering is faster. However, if the response is dynamic, SSR is a better choice. Sometimes, a hybrid approach may be the best for the situation. The static rendering is beyond the scope of this article. We will cover it for another time.

如果页面仅包含静态数据,则静态渲染速度更快。 但是,如果响应是动态的,则SSR是更好的选择。 有时,混合方法可能是最适合这种情况的方法。 静态呈现超出了本文的范围。 我们将再讨论一次。

Thanks for reading. I hope this was helpful. You can see my other Medium publications here.

谢谢阅读。 我希望这可以帮到你。 您可以在这里查看我的其他Medium出版物。

翻译自: https://medium.com/javascript-in-plain-english/a-hands-on-guide-for-a-server-side-rendering-react-app-dd1efa3ec0d8

react 服务器端渲染


http://www.taodudu.cc/news/show-5536396.html

相关文章:

  • SpringBoot+Mybatis-Plus+Thymeleaf+Bootstrap分页查询(前后端完整版开源学习)图书管理系统
  • idea与vue+ElementUI搭建前后端分离的CRUD
  • datatables服务器端分页
  • 网购秒杀系统案例分析
  • 【架构设计】————12、网购秒杀系统架构设计案例分析
  • Java内置线程池ExecutorService介绍及商品秒杀案例
  • 《大型网站技术架构:核心原理与案例分析》-- 读书笔记 (5) :网购秒杀系统...
  • 电商之库存超卖或者秒杀超卖问题
  • 案例3:网购秒杀系统架构设计案例
  • 网购秒杀系统架构设计案例分析——《大型网站技术架构》笔记
  • 网购秒杀系统架构设计分析
  • 网购秒杀系统的设计
  • 网购秒杀系统架构案例分析
  • 系统架构设计——网购秒杀系统架构设计
  • 自己实现一个简单的网购秒杀系统
  • 网购秒杀系统架构设计案例分析
  • 网购秒杀的系统架构设计
  • Yarn 和 NPM 国内镜像(淘宝镜像)
  • 淘宝招聘邮箱
  • 陈华:淘宝同学左侧导航栏div鼠标上浮边框变色无遮挡处理方法
  • DLINK路由器LAN网段笔记本无法连上LAN网段打印机
  • D-link850路由器漏洞挖掘与利用
  • dlink路由器带来的麻烦
  • Dlink路由器模拟器
  • dlink路由器设置虚拟服务器,D-Link路由器端口转发设置图文教程
  • Dlink路由器 CNVD-2018-01084 远程命令执行漏洞 复现分析
  • numpy的矩阵求逆
  • php命名空间与引入
  • PHP空间出现session无法保存问题解决办法
  • php获取当前命名空间所有类,PHP – 获取特定命名空间内的所有类名

react 服务器端渲染_服务器端渲染React应用程序的动手指南相关推荐

  1. react前端项目_如何使用React前端设置Ruby on Rails项目

    react前端项目 The author selected the Electronic Frontier Foundation to receive a donation as part of th ...

  2. react java编程_快速上手React编程 PDF 下载

    资料目录: 第1章  初积React  3 1.1  什么是React  4 1.2 React解决的问题  5 1.3  使用React的好处  6 1.3.1  简单性  6 1.3.2  速度和 ...

  3. python 服务端渲染_客户端渲染与服务端渲染

    后端渲染 服务器直接生成HTML文档并返回给浏览器,但页面交互能力有限.适用于任何后端语言:PHP.Java.Python.GO等. 客户端渲染(CSR) 页面初始加载的HTML文档中无内容,需要下载 ...

  4. react前端开发_是的,React正在接管前端开发。 问题是为什么。

    react前端开发 by Samer Buna 通过Samer Buna 是的,React正在接管前端开发. 问题是为什么. (Yes, React is taking over front-end ...

  5. react jest测试_如何使用React测试库和Jest开始测试React应用

    react jest测试 Testing is often seen as a tedious process. It's extra code you have to write, and in s ...

  6. react实战课程_在使用React一年后,我学到的最重要的课程

    react实战课程 by Tomas Eglinskas 由Tomas Eglinskas 在使用React一年后,我学到的最重要的课程 (The most important lessons I'v ...

  7. react hooks使用_何时使用React Suspense和React Hooks

    react hooks使用 React Suspense对Monad就像钩子对应用符号一样 (React Suspense is to a Monad as Hooks are to Applicat ...

  8. react 执行入口_如何使用React执行CRUD操作

    react 执行入口 by Zafar Saleem 通过Zafar Saleem 如何使用React执行CRUD操作 (How to perform CRUD operations using Re ...

  9. react测试组件_如何测试React组件:完整指南

    react测试组件 When I first started learning to test my apps back in the day, I would get very frustrated ...

最新文章

  1. 谈谈分布式的场景及分布式事务的解决方案
  2. How to think positively 如何培养正念
  3. 学习MySQL / MariaDB初学者 - 第1部分
  4. mysql执行代码段_mysql的event schedule 可以让你设置你的mysql数据库再某段时间执行你想要的动作...
  5. Deploy Oracle 10.2.0.5 DataGuard on Red Hat Enterprise Linux 6.4
  6. 关于博主 | 联系博主
  7. 浅谈几种常见 RAID 的异同
  8. EBS Sql Loader的简单使用
  9. 台达b2伺服modbus通讯_谁用电脑与台达ASDA-B2伺服通讯上-专业自动化论坛-中国工控网论坛...
  10. Android兼容性测试应该怎么做逼格更高呢?
  11. ubuntu创建“新建文本文档”的快捷方式
  12. 智能电视怎么安装鸿蒙,智能电视如何通过手机电脑安装第三方软件,两种操作方法亲测可用...
  13. 饿了么高级设计师:界面视觉设计 5 要素
  14. 2011 9 11最新过QQ游戏检测Cheat Engine(CE)搜索数据
  15. 咪咕阅读怎么下载小说
  16. pygame简易超级玛丽制作
  17. .netMVC企业微信网页授权+注册全局过滤器
  18. 图文详解 DBMS 数据库管理系统三层架构体系(三级模式)《ClickHouse 实战:企业级大数据分析引擎》...
  19. 转贴:粒子在施瓦西黑洞内部是如何运动的?
  20. chrome滚动条样式修改

热门文章

  1. python简笔画_不给糖果就捣乱,用 Python 绘制有趣的万圣节南瓜怪
  2. MVC中使用PartialView方法
  3. iPhone, iPad升级ios7正式版教程
  4. Android11取消强制加密,悲催!Android 6.0设备强制要求开启全盘加密
  5. 病毒木马查杀实战第020篇:Ring3层主动防御之基本原理
  6. 一例人肉搜索-伊凡娜丢失的Sidekick手机
  7. 佩戴舒适的蓝牙耳机推荐,不堵耳朵的骨传导耳机
  8. 复盘:一款产品从0到1的全过程
  9. 7-4 网红点打卡攻略(dfs)
  10. html免流脚本,免流一键搭建脚本跑商速度