服务器端渲染,也称为SSR,是指JavaScript应用程序在服务器上而不是在浏览器中渲染的能力。
我们为什么要这样做呢?
- 它允许你的网站有更快的首页加载时间,这是良好用户体验的关键。
- 它对搜索引擎优化至关重要:搜索引擎不能(还不能)有效和正确地索引完全在客户端渲染的应用程序。尽管谷歌在索引方面有最新的改进,但也有其他搜索引擎,而且谷歌在任何情况下都不是完美的。此外,谷歌偏爱加载时间快的网站,而必须在客户端加载对速度没有好处。
- 当人们在社交媒体上分享你的网站页面时,这很好,因为他们可以很容易地收集所需的元数据来很好地分享链接(图片、标题、描述...)。
如果没有服务器端渲染,你的服务器只提供一个没有主体的HTML页面,只有一些脚本标签,然后由浏览器用来渲染应用程序。
客户端渲染的应用程序在第一次页面加载后的任何后续用户互动方面都很出色。服务器端渲染允许我们在客户端渲染的应用程序和后端渲染的应用程序之间获得甜头:页面在服务器端生成,但一旦页面被加载,所有与页面的互动都在客户端处理。
然而,服务器端渲染也有其缺点。
- 可以说,一个简单的SSR概念证明是简单的,但SSR的复杂性会随着你的应用的复杂性而增加
- 在服务器端渲染一个大的应用程序可能是相当耗费资源的,在重载情况下,它甚至可能提供比客户端渲染更慢的体验,因为你有一个单一的瓶颈。
一个非常简单的例子,说明在服务器端渲染React应用所需要的东西。
SSR的设置可以变得非常非常复杂,大多数教程从一开始就会加入Redux、React Router和其他许多概念。
为了理解SSR的工作原理,让我们从基础开始,实现一个概念验证。
如果你只想了解提供SSR的库,而不想做基础工作,可以随意跳过这一段。
为了实现基本的SSR,我们将使用Express。
如果你是Express的新手,或者需要补习一下,请查看我的免费Express手册:https://flaviocopes.com/page/ebooks/。
警告:SSR的复杂性可以随着你的应用程序的复杂性而增加。这是渲染一个基本的React应用的最低设置。对于更复杂的需求,你可能需要做更多的工作,或者也可以检查一下React的SSR库。
我假设你用create-react-app 开始了一个React应用。如果你只是尝试,现在用npx create-react-app ssr 安装一个。
用终端进入主应用程序文件夹,然后运行。
你的app目录下有一组文件夹。创建一个名为server 的新文件夹,然后进入其中并创建一个名为server.js 的文件。
按照create-react-app 的惯例,应用程序住在src/App.js 文件中。我们将加载该组件,并使用ReactDOMServer.renderToString()将其渲染成一个字符串,该组件由react-dom 提供。
你得到./build/index.html 文件的内容,并将<div id="root"></div> 占位符(即应用程序默认钩住的标签)替换为````。
${ReactDOMServer.renderToString(
)}
`.
build 文件夹中的所有内容都将被Express静态地提供。
import path from 'path'
import fs from 'fs'
import express from 'express'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import App from '../src/App'
const PORT = 8080
const app = express()
const router = express.Router()
const serverRenderer = (req, res, next) => {
fs.readFile(path.resolve('./build/index.html'), 'utf8', (err, data) => {
if (err) {
console.error(err)
return res.status(500).send('An error occurred')
}
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(<App />)}</div>`
)
)
})
}
router.use('^/$', serverRenderer)
router.use(
express.static(path.resolve(__dirname, '..', 'build'), { maxAge: '30d' })
)
// tell the app to use the above rules
app.use(router)
// app.use(express.static('./build'))
app.listen(PORT, () => {
console.log(`SSR running on port ${PORT}`)
})
现在,在客户端应用程序中,在你的src/index.js ,而不是调用ReactDOM.render() 。
ReactDOM.render(<App />, document.getElementById('root'))
调用 ReactDOM.hydrate(),这是相同的,但有额外的能力,可以在React加载后将事件监听器附加到现有的标记。
ReactDOM.hydrate(<App />, document.getElementById('root'))
所有的Node.js代码都需要被Babel转译,因为服务器端的Node.js代码不了解JJSX,也不了解ES模块(我们在include 语句中使用)。
安装这4个软件包。
npm install @babel/register @babel/preset-env @babel/preset-react ignore-styles
ignore-styles是一个Babel工具,将告诉它忽略使用import 语法导入的CSS文件。
让我们在server/index.js 中创建一个入口点。
require('ignore-styles')
require('@babel/register')({
ignore: [/(node_modules)/],
presets: ['@babel/preset-env', '@babel/preset-react']
})
require('./server')
构建React应用程序,使build/文件夹被填充。
并让我们运行这个。
我说过这是一个简单的方法,确实如此。
- 当使用导入时,它不能正确地处理图像的渲染,因为导入需要Webpack才能工作(这使过程变得非常复杂)。
- 它没有处理页面标题元数据,这对于SEO和社会共享的目的是必不可少的(除其他外)。
因此,虽然这是一个使用ReactDOMServer.renderToString() 和ReactDOM.hydrate 来获得基本的服务器端渲染的好例子,但对于现实世界的使用来说是不够的。
使用库的服务器端渲染
SSR很难做对,React也没有事实上的方法来实现它。
为了获得这些好处,而不使用不同的技术来服务这些页面,是否值得付出这样的麻烦、复杂和开销,仍然是非常值得商榷的。Reddit上的这个讨论在这方面有很多意见。
当服务器端渲染是一个重要的问题时,我的建议是依靠预制的库和工具,它们从一开始就考虑到了这个目标。