什么是React SSR?什么情况下进行SSR?
React SSR即基于React框架的服务端渲染。 当我们要求渲染时间尽量快、页面响应速度快时(优点),才会采用服务器渲染,并且应该“按需”对页面进行渲染——“首次加载/首屏”。即服务端渲染的特点在于:由中间层( node端 )为客户端请求初始数据、并由node渲染页面。
客户端渲染路线
- 请求一个html
- 服务端返回一个html
- 浏览器下载html里面的js/css文件
- 等待js文件下载完成
- 等待js加载并初始化完成
- js代码终于可以运行,由js代码向后端请求数据( ajax/fetch )
- 等待后端数据返回
- react-dom( 客户端 )从无到完整地,把数据渲染为响应页面.
服务端渲染路线
- 请求一个html
- 服务端请求数据( 内网请求快 )
- 服务器初始渲染(服务端性能好,较快)
- 服务端返回已经有正确内容的页面
- 客户端请求js/css文件
- 等待js文件下载完成
- 等待js加载并初始化完成
- react-dom( 客户端 )把剩下一部分渲染完成( 内容小,渲染快 )
时间耗时对比
- 数据请求:由服务端请求数据而不是客户端请求数据,这是“快”的一个主要原因。服务端在内网进行请求,数据响应速度快。客户端在不同网络环境进行数据请求,且外网http请求开销大,导致时间差(主要原因)。
- 步骤:服务端是先请求数据然后渲染“可视”部分,而客户端是等待js代码下载、加载完成再请求数据、渲染。即:服务端渲染不用等待js代码下载完成再请求数据,并会返回一个已经有内容的页面。
- 渲染性能:服务端性能比客户端高,渲染速度快( 猜测,该项数据不详 )。
- 渲染内容:服务端渲染会把”可视“部分先渲染,然后交给客户端再作部分渲染。而客户端渲染,则是从无到有,需要经历完整的渲染步骤。
优势总结
- 首次加载页面的速度加快。客户端渲染的一个缺点是,当用户第一次进入站点,此时浏览器中没有缓存,需要下载代码后在本地渲染,时间较长。而服务器渲染则是,用户在下载的已经是渲染好的页面了,打开速度比本地渲染快。
- SEO。服务器端渲染可以让搜索引擎更容易读取页面的meta信息以及其他SEO相关信息,大大增加网站在搜索引擎中的可见度。
什么是Next.js
Next.js 是一个轻量级的 React 服务端渲染应用框架。
框架特点
- 使用后端渲染
- 自动进行代码分割,以获得更快的网页加载速度
- 简洁的前端路由实现
- 使用webpack进行构建,支持模块热更新
- 可与主流Node服务器进行对接(如express)
- 可自定义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
完事。