Next.js构建React SSR

293 阅读4分钟

什么是React SSR?什么情况下进行SSR?

React SSR即基于React框架的服务端渲染。 当我们要求渲染时间尽量快、页面响应速度快时(优点),才会采用服务器渲染,并且应该“按需”对页面进行渲染——“首次加载/首屏”。即服务端渲染的特点在于:由中间层( node端 )为客户端请求初始数据、并由node渲染页面。

客户端渲染路线

  1. 请求一个html
  2. 服务端返回一个html
  3. 浏览器下载html里面的js/css文件
  4. 等待js文件下载完成
  5. 等待js加载并初始化完成
  6. js代码终于可以运行,由js代码向后端请求数据( ajax/fetch )
  7. 等待后端数据返回
  8. react-dom( 客户端 )从无到完整地,把数据渲染为响应页面.

服务端渲染路线

  1. 请求一个html
  2. 服务端请求数据( 内网请求快 )
  3. 服务器初始渲染(服务端性能好,较快)
  4. 服务端返回已经有正确内容的页面
  5. 客户端请求js/css文件
  6. 等待js文件下载完成
  7. 等待js加载并初始化完成
  8. react-dom( 客户端 )把剩下一部分渲染完成( 内容小,渲染快 )

时间耗时对比

  1. 数据请求:由服务端请求数据而不是客户端请求数据,这是“快”的一个主要原因。服务端在内网进行请求,数据响应速度快。客户端在不同网络环境进行数据请求,且外网http请求开销大,导致时间差(主要原因)。
  2. 步骤:服务端是先请求数据然后渲染“可视”部分,而客户端是等待js代码下载、加载完成再请求数据、渲染。即:服务端渲染不用等待js代码下载完成再请求数据,并会返回一个已经有内容的页面。
  3. 渲染性能:服务端性能比客户端高,渲染速度快( 猜测,该项数据不详 )。
  4. 渲染内容:服务端渲染会把”可视“部分先渲染,然后交给客户端再作部分渲染。而客户端渲染,则是从无到有,需要经历完整的渲染步骤。

优势总结

  1. 首次加载页面的速度加快。客户端渲染的一个缺点是,当用户第一次进入站点,此时浏览器中没有缓存,需要下载代码后在本地渲染,时间较长。而服务器渲染则是,用户在下载的已经是渲染好的页面了,打开速度比本地渲染快。
  2. SEO。服务器端渲染可以让搜索引擎更容易读取页面的meta信息以及其他SEO相关信息,大大增加网站在搜索引擎中的可见度。

什么是Next.js

Next.js 是一个轻量级的 React 服务端渲染应用框架。

框架特点

  1. 使用后端渲染
  2. 自动进行代码分割,以获得更快的网页加载速度
  3. 简洁的前端路由实现
  4. 使用webpack进行构建,支持模块热更新
  5. 可与主流Node服务器进行对接(如express)
  6. 可自定义babel和webpack的配置

使用方法

创建项目并初始化

mkdir next-demo
cd next-demo
npm init -y

安装next.js

因为是创建React应用,所以同时安装react和react-dom

npm install --save react react-dom next

在项目根目录下添加文件夹pages(一定要命名为pages,这是next的强制约定,不然会导致找不到页面),然后创建index.js

import Link from "next/link";
import Layout from "../components/layout/layout";

const Index = (props) => (
    <Layout>
        <ul>
            <li>
                <Link href={`/detail?id=666`}>
                    <a>666</a>
                </Link>
            </li>
        </ul>
        <style jsx>
            {`
                a{
                  color: brown;
                  font-size: 18px;
                }
            `}
        </style>
    </Layout>
)

export default Index

路由伪装(Route Masking)

所谓的路由伪装即让浏览器地址栏显示的url和页面实际访问的url不一样。实现路由伪装的方法也很简单,通过Link组件的as属性告诉浏览器href对应显示为什么url就可以了

<Link as={`/p/666`} href={`/detail?id=666`}>
    <a>666</a>
</Link>

浏览器的url已经被如期修改了,这样看起来舒服多了。但是如果你刷新详情页,会报404的错误。这是因为刷新页面会直接向服务器请求这个url,而服务端并没有为该URL配置路由。为了解决这个问题,需要用到next.js提供的自定义服务接口(custom server API)。

自定义服务接口

自定义服务接口前我们需要创建服务器,安装Express:

npm install --save express

在项目根目录下创建server.js 文件,内容如下:

const express = require('express');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const port = dev ? 3003 : 3004;
const app = next({dev});
const handle = app.getRequestHandler();

app.prepare()
    .then(() => {
        const server = express();
        server.get('/p/:id', (req, res) => {
            const actualPage = '/detail';
            const queryParams = {id: req.params.id};
            app.render(req, res, actualPage, queryParams);
        });
        server.get('*', (req, res) => {
            return handle(req, res);
        });
        server.listen(port, (err) => {
            if (err) throw err;
            console.log(`> Ready on http://localhost:${port}`);
        });
    })
    .catch((ex) => {
        console.error(ex.stack);
        process.exit(1);
    });

然后将package.json里面的dev script改为:

"scripts": {
    "dev": "node server.js"
}

重新运行就没问题了。

远程数据获取

next.js提供了一个标准的获取远程数据的接口:getInitialProps,通过getInitialProps我们可以获取到远程数据并赋值给页面的props。首先,我们安装isomorphic-unfetch,它是基于fetch实现的一个网络请求库

npm install --save isomorphic-unfetch

然后我们修改index.js如下:

import Link from "next/link";
import fetch from "isomorphic-unfetch";
import Layout from "../components/layout/layout";

const Index = (props) => (
    <Layout>
        <ul>
            {props.shows.map(({ show }) => {
                return (
                    <li key={show.id}>
                        <Link as={`/p/${show.id}`} href={`/detail?id=${show.id}`}>
                            <a>{show.name}</a>
                        </Link>
                    </li>
                );
            })}
        </ul>
        <style jsx>
            {`
                a{
                  color: brown;
                  font-size: 18px;
                }
            `}
        </style>
    </Layout>
)

Index.getInitialProps = async function () {
    const res = await fetch('https://api.tvmaze.com/search/shows?q=marvel');
    const data = await res.json();
    return {
        shows: data
    }
}

export default Index

接下来我们来实现详情页,将id作为参数去获取电视节目的详细内容,接下来在pages文件夹下创建detail.js

import fetch from "isomorphic-unfetch"
import Layout from "../components/layout/layout";

const Detail = (props) => {
    return <Layout>
        <div className={"detail"}>
            <h1>{props.show.name}</h1>
            <p>{props.show.summary.replace(/<[/]?p>/g, '')}</p>
            <img src={props.show.image.medium} />
        </div>
        <style jsx>
            {`
                .detail{
                  text-align: center;
                }
                .detail p{
                  text-align: left;
                }
            `}
        </style>
    </Layout>
};

Detail.getInitialProps = async function (context) {
    const { id } = context.query;
    const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
    const show = await res.json();
    return { show };
}

export default Detail;

部署next.js应用

将package.json里面的script改为

"scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  }

执行命令

npm run build
npm run start

完事。