最新消息:看到那些跳动的图片、文字了吗?点击点击 O(∩_∩)O~~

React SSR 探索 —— 服务器端渲染出组件

若思若想 onlyling 1927浏览

SSR -> Server-side rendering

服务器端渲染

背景

有些项目里不得不做一些 SEO 的东西,但不想用太传统的方案。

有些时候,明明知道它并没有什么用,但就想试一试。

基于种种有用没用的需求,决定从头到尾搞一次 React 服务器端渲染,虽然市面上已经有蛮多方案了,例如 Next.js,但经历一遍才会知道有哪些坑、需要注意些什么、为什么他们要这样做等等。

React SSR 解决了什么问题

客户端使用 React 开发,大有可能已经采用了基于浏览器的前后端分离开发模式,那么,这种开发模式下,页面是如何呈现的呢。

大致经历这样几个过程:

  1. 浏览器向服务器发起请求,拉取静态的 HTML 文件;
  2. 浏览器解析静态文件,拉取需要的 CSS、JavaScript 文件(现阶段浏览器一片空白,我们可以在静态文件里面加入一个加载中的动画效果,告诉用户我们在干活了,不要着急);
  3. 浏览器执行 JavaScript,根据业务代码,显示一个初始化的页面,再执行当前路由对应的事件(现阶段浏览器已经有了最基本的页面框架,但是没有动态内容,此时应该有各种异步请求发起);
  4. 待异步请求结束后,可能会重新渲染页面,呈现完整的内容;

在执行 JavaScript 的时候,可能会因为浏览器不支持新 API 的问题,页面报错,功亏一篑。

采用 React 服务器端渲染呢,前面几个过程都相似,只是在第二步的时候,页面已经能完成的呈现了。

那么,React 服务器端渲染大概可以解决如下几个问题。

搜索引擎友好(SEO)

在做一些需要搜索引擎排名的页面事,会受到很大的阻碍。

例如页面的首页,一般我们看到的是这样的源代码,里面有很多内容可以呈现,并且内链完善。

基于浏览器前后端分离的页面看到了如下的源代码,只有简单的 <div id=root></div>,完全不知道这个页面是什么主题、内容。

对于国内不太聪明的搜索引擎爬虫,第二个页面没有什么价值,相同关键词搜索结果排名不会靠前。

开发很爽,上线后的结果却很痛。

提高首屏渲染能力

访问客户端设备的硬件性能参差不齐,导致不同设备的用户看到不同的加载状态,可能某些比较差的设备,第一屏的内容还没有呈现出来,浏览器就挂了、无响应等。

通过 React 服务器端渲染这么一倒腾,服务器响应的内容已经是一个完整的页面,浏览器只需要一次渲染就能看到。

比较直观的对比,可以参考 北斗-打造高可靠与高性能的React同构解决方案 这里的动图对比。

服务器端渲染一个组件

浏览器端渲染一个组件

import React from 'react';
import ReactDOM from 'react-dom';

const Node = () => {
    return <div>浏览器端渲染一个组件</div>;
}

ReactDOM.render(<Node />, document.getElementById('root'));

如何在服务器端渲染呢,根据 React 的 文档 提示,只需要小小改动一下就好了。

import React from 'react';
import ReactDOMServer from 'react-dom/server';

const Node = () => {
    return <div>浏览器端渲染一个组件</div>;
}

const SERVER_HTML = ReactDOMServer.renderToString(Node);

console.log(SERVER_HTML);

打印 SERVER_HTML 这个变量,就会看到它就是 <div>浏览器端渲染一个组件</div>

搭建一个服务器端渲染的服务器

文件结构

|-- 根目录
|--|-- http.js ## Node.js 服务端启动文件
|--|-- entry-server.jsx ## React 服务器端入口文件
|--|-- app.jsx ## React 根组件
|--|-- webpack.config.js ## webpack 配置文件

webpack 配置文件 webpack.config.js

// 比较简单,其他省略了,主要是把 ES6、JSX 的代码编译,然后打包成 commonjs 的包,提供给服务端使用。
const config = {
    entry: {
        app: './entry-server.js'
    },
    output: {
        path: './',
        filename: 'ssr.js',
        libraryExport: 'default',
        libraryTarget: 'commonjs2'
    }
}

React 根组件 app.jsx

import React from 'react';

const Node = () => {
    return <div>浏览器端渲染一个组件</div>;
}

export default Node;

React 服务器端入口文件 entry-server.jsx

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './app';

export default renderHTML = () => {
    return ReactDOMServer.renderToString(App);
};

Node.js 服务端启动文件 http.js

const express = require('express');
const SSR = require('./ssr');

const app = express();

app.get('*', (req, res) => {
    const __html = SSR();

    res.send(`
        <!DOCTYPE html>
        <html lang="en">
            <head>
                <title>TEST SSR</title>
            </head>
            <body>
                ${__html}
            </body>
        </html>
    `);
});

app.listen(3000, () => {
    console.log('Running on http://localhost:3000/');
});

按道理说这样子就能看到效果了,以上代码是临时改造的,不能保障正常运营,过程是正确的。

加入 react-router

新增 routes.jsx 文件

import React from 'react';
import { Switch, Redirect, Route } from 'react-router-dom';

const PageHome = () => {
    return <div>Home</div>
}

const PageList = () => {
    return <div>List</div>
}

const Page404 = () => {
    return <div>404</div>
}

export default () => {
    return (
        <Switch>
            <Redirect exact from="/" to="/home" />
            <Route exact path="/home" component={PageHome} />
            <Route exact path="/list" component={PageList} />
            <Route component={Page404} />
        </Switch>
    );
};

修改 app.jsx

import React from 'react';
import { Link } from 'react-router-dom';
import Routes from './routes';

const Node = () => {
    return (
        <div>
            <Link to="/home">Home</Link>&emsp;
            <Link to="/list">List</Link>&emsp;
            <Link to="/not-find">404</Link>
            <Routes />
        </div>
    );
}

export default Node;

修改 entry-server.jsx

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import App from './app';

export default renderHTML = (url) => {

    let context = {};
    const RootApp = (
        <StaticRouter location={url} context={context}>
            <App />
        </StaticRouter>
    );

    if (context.url) {
        // 如果需要重定向
        // <Redirect exact from="/" to="/home" />
        // 重定向到指定路由
        return {
            context
        }
    }

   const html = ReactDOMServer.renderToString(RootApp);

   return {
        context,
        html
   }
};

修改 http.js

app.get('*', (req, res) => {
    const ssr = SSR();

    if(ssr.context.url) {
        return res.redirect(rendered.context.url);  }

    res.send(`
        <!DOCTYPE html>
        <html lang="en">
            <head>
                <title>TEST SSR</title>
            </head>
            <body>
                ${ssr.html}
            </body>
        </html>
    `);
});

以上操作中,已经完成了一个最简单的静态(没有异步获取数据)应用的服务器端渲染。

转载请注明:OnlyLing - Web 前端开发者 » React SSR 探索 —— 服务器端渲染出组件