随着前端技术的不断发展,服务端渲染(Server-Side Rendering,SSR)变得越来越流行。与传统的客户端渲染(Client-Side Rendering,CSR)相比,SSR 能够提供更好的 SEO、更快的首屏渲染速度以及更好的用户体验。
在本文中,我们将使用 Koa2 框架和 React 库来搭建一个基于 SSR 的应用,并详细介绍其实现过程和相关技术细节。
环境准备
在开始之前,我们需要先安装 Node.js 和 npm。可以前往 Node.js 官网 下载并安装。
安装完毕后,可以通过以下命令来检查是否安装成功:
node -v npm -v
接着,我们需要创建一个空的项目目录,并在其中初始化 npm:
mkdir koa2-react-ssr cd koa2-react-ssr npm init -y
安装依赖
接着,我们需要安装一些依赖:
npm install --save koa koa-router koa-static koa-bodyparser react react-dom react-router-dom react-helmet npm install --save-dev webpack webpack-cli webpack-dev-middleware webpack-hot-middleware babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties @babel/plugin-transform-runtime @babel/runtime nodemon
这些依赖分别是:
koa
:Koa2 框架;koa-router
:Koa2 路由;koa-static
:Koa2 静态文件服务;koa-bodyparser
:Koa2 请求体解析器;react
:React 库;react-dom
:React DOM 渲染器;react-router-dom
:React 路由;react-helmet
:React 网页 head 管理;webpack
:Webpack 打包工具;webpack-cli
:Webpack 命令行工具;webpack-dev-middleware
:Webpack 开发中间件;webpack-hot-middleware
:Webpack 热更新中间件;babel-loader
:Babel 加载器;@babel/core
:Babel 核心库;@babel/preset-env
:Babel 环境预设;@babel/preset-react
:Babel React 预设;@babel/plugin-proposal-class-properties
:Babel 类属性插件;@babel/plugin-transform-runtime
:Babel 运行时插件;@babel/runtime
:Babel 运行时库;nodemon
:Node.js 自动重启工具。
目录结构
接着,我们需要创建一些目录和文件:
// javascriptcn.com 代码示例 koa2-react-ssr/ |- public/ | |- index.html | |- bundle.js | |- src/ | |- server/ | | |- index.js | | |- App.js | | |- routes.js | | |- template.js | | | |- client/ | |- index.js | |- App.js | |- routes.js | |- webpack.config.js |- .babelrc |- nodemon.json |- package.json
其中,public/
目录用于存放前端静态文件,src/server/
目录用于存放服务端代码,src/client/
目录用于存放前端代码。
配置文件
接着,我们需要配置一些文件。
webpack.config.js
首先是 webpack.config.js
文件:
// javascriptcn.com 代码示例 const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const isDev = process.env.NODE_ENV === 'development'; module.exports = { mode: isDev ? 'development' : 'production', entry: isDev ? [ 'webpack-hot-middleware/client', './src/client/index.js', ] : './src/client/index.js', output: { path: path.resolve(__dirname, 'public'), filename: 'bundle.js', publicPath: '/', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ '@babel/preset-env', '@babel/preset-react', ], plugins: [ '@babel/plugin-proposal-class-properties', '@babel/plugin-transform-runtime', ], }, }, }, ], }, plugins: [ new HtmlWebpackPlugin({ template: './src/server/template.js', filename: 'index.html', }), isDev && new webpack.HotModuleReplacementPlugin(), ].filter(Boolean), devtool: isDev ? 'eval-source-map' : 'hidden-source-map', };
这个文件主要做了以下事情:
- 根据环境变量决定是否开启开发模式;
- 配置入口文件、输出文件和静态文件路径;
- 配置 Babel 加载器;
- 配置 HTMLWebpackPlugin 生成 HTML 文件;
- 配置热更新插件;
- 配置 Source Map。
.babelrc
接着是 .babelrc
文件:
// javascriptcn.com 代码示例 { "presets": [ "@babel/preset-env", "@babel/preset-react" ], "plugins": [ "@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime" ] }
这个文件主要是配置 Babel 的预设和插件。
nodemon.json
最后是 nodemon.json
文件:
{ "watch": [ "src/server" ], "ext": "js", "exec": "babel-node src/server/index.js" }
这个文件主要是配置 Nodemon 的监视目录和执行命令。
编写代码
有了上面的准备工作,我们就可以开始编写代码了。
服务端
首先是服务端代码,我们需要在 src/server/
目录下创建以下文件:
index.js
// javascriptcn.com 代码示例 import Koa from 'koa'; import Router from 'koa-router'; import serve from 'koa-static'; import bodyParser from 'koa-bodyparser'; import { renderToString } from 'react-dom/server'; import { StaticRouter } from 'react-router-dom'; import { Helmet } from 'react-helmet'; import App from './App'; import routes from './routes'; import template from './template'; const app = new Koa(); const router = new Router(); router.get('*', async (ctx) => { const context = {}; const content = renderToString( <StaticRouter location={ctx.url} context={context}> <App routes={routes} /> </StaticRouter>, ); const helmet = Helmet.renderStatic(); const html = template({ content, helmet }); ctx.body = html; }); app.use(bodyParser()); app.use(serve('public')); app.use(router.routes()); app.listen(3000, () => { console.log('Server is listening on http://localhost:3000'); });
这个文件主要做了以下事情:
- 导入 Koa、Koa 路由、Koa 静态文件服务、Koa 请求体解析器、React DOM 渲染器、React 路由、React 网页 head 管理、应用组件和路由配置;
- 创建 Koa 实例和 Koa 路由实例;
- 配置路由,使用 React DOM 渲染器将应用组件渲染成 HTML 字符串,使用 React 路由将 HTML 字符串嵌入到网页中,使用 React 网页 head 管理更新网页 head;
- 使用 Koa 请求体解析器和 Koa 静态文件服务中间件;
- 启动 Koa 服务器。
App.js
// javascriptcn.com 代码示例 import React from 'react'; import { Route, Switch } from 'react-router-dom'; const App = ({ routes }) => ( <Switch> {routes.map(({ path, exact, component: Component }) => ( <Route key={path} path={path} exact={exact} render={(props) => <Component {...props} routes={routes} />} /> ))} </Switch> ); export default App;
这个文件主要是应用组件,用于渲染路由。
routes.js
// javascriptcn.com 代码示例 import Home from './Home'; import About from './About'; const routes = [ { path: '/', exact: true, component: Home, }, { path: '/about', exact: true, component: About, }, ]; export default routes;
这个文件主要是路由配置。
template.js
// javascriptcn.com 代码示例 const template = ({ content, helmet }) => ` <!DOCTYPE html> <html> <head> ${helmet.title.toString()} ${helmet.meta.toString()} ${helmet.link.toString()} </head> <body> <div id="root">${content}</div> <script src="/bundle.js"></script> </body> </html> `; export default template;
这个文件主要是 HTML 模板,用于生成 HTML 文件。
客户端
接着是客户端代码,我们需要在 src/client/
目录下创建以下文件:
index.js
// javascriptcn.com 代码示例 import React from 'react'; import { BrowserRouter } from 'react-router-dom'; import { hydrate } from 'react-dom'; import App from './App'; import routes from './routes'; hydrate( <BrowserRouter> <App routes={routes} /> </BrowserRouter>, document.getElementById('root'), ); if (module.hot) { module.hot.accept(); }
这个文件主要做了以下事情:
- 导入 React、React DOM 渲染器、React 路由、应用组件和路由配置;
- 使用 React DOM 渲染器将应用组件渲染到网页上;
- 配置热更新。
App.js
同服务端的 App.js
。
routes.js
同服务端的 routes.js
。
运行应用
最后,我们可以使用以下命令来运行应用:
npm run dev
这个命令会启动 Webpack 和 Node.js 服务器,并在浏览器中打开 http://localhost:3000
。
总结
在本文中,我们使用 Koa2 框架和 React 库来搭建了一个基于 SSR 的应用,并详细介绍了其实现过程和相关技术细节。希望这篇文章能够对您有所帮助。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/656b62d7d2f5e1655d3c2f36