前言
随着React的流行,越来越多的企业也开始转向使用React技术栈。而React作为一款客户端渲染技术,通常会遇到一些SEO和首次加载速度较慢的问题,这时候我们可以考虑使用React的SSR技术。本篇文章将介绍如何利用Hapi框架搭建React SSR应用,并解决在实践过程中可能遇到的问题。
Hapi介绍
Hapi是一个Node.js的Web框架,它拥有很好的可扩展性、可重用性和可测试性,是一个非常适合于构建企业级Web应用的框架。它的最大的优点在于可以通过插件的形式使得开发者可以快速地搭建出各种各样的Web应用。在本篇文章中,我们会使用Hapi框架作为React SSR应用的框架。
React SSR应用
SSR,即Server Side Rendering,是指在服务器端渲染React组件,将渲染后的HTML字符串发送到客户端,然后再由客户端接管组件的交互。这样做可以达到减轻客户端负担、快速首屏渲染、SEO优化等优点。
React SSR的实现
关于React SSR的实现,有很多种不同的方案,本文采用以下的技术栈:
- React / ReactDOM
- React Router
- Redux
- Hapi
以下是一个简单的React SSR应用的相关代码:
// javascriptcn.com 代码示例 import React from 'react'; import { Provider } from 'react-redux'; import { renderToString } from 'react-dom/server'; import { RouterContext, match } from 'react-router'; import configureStore from 'store'; import routes from 'routes'; const renderFullPage = (appHtml, initialState) => { return `<!doctype html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>React SSR</title> </head> <body> <div id="root">${appHtml}</div> <script> window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}; </script> <script src="./bundle.js"></script> </body> </html>`; }; const serverRenderer = (request, reply) => { const { url } = request; const store = configureStore(); match({ routes, location: url }, (err, redirectLocation, renderProps) => { if (err) { return reply(err.message).code(500); } if (!renderProps) { return reply('Not Found').code(404); } const appHtml = renderToString( <Provider store={store}> <RouterContext {...renderProps}/> </Provider> ); const initialState = store.getState(); reply(renderFullPage(appHtml, initialState)); }); }; export default serverRenderer;
Hapi与React SSR结合的问题及解决策略
路由配置
React SSR应用的路由通常使用React Router来定义。在Hapi框架中,我们可以使用hapi-router和inert插件来完成路由配置。其中,hapi-router插件可以把指定路径下的.js文件扩展名过滤器的文件,自动注册为Hapi插件,且在指定路径下创建RESTful API路由。而inert插件可以很方便地获取静态文件。
以下是一个路由配置的示例代码:
// javascriptcn.com 代码示例 import path from 'path'; const router = { path: path.join(__dirname), routes: { prefix: '/api', }, }; const register = (server) => { server.register([ { register: require('inert') }, { register: require('hapi-router'), options: router }, ], (err) => { if (err) { console.error('Failed to load a plugin:', err); } }); }; register.attributes = { name: 'Router', }; export default register;
静态文件路径问题
在Hapi框架中,我们可以使用静态文件插件inert来获取静态文件。但是,当我们使用了React Router对应用进行路由管理后,我们的静态文件路径就会出现问题。解决这个问题的关键在于我们要把所有的路由都重定向到/index.html,见下面示例代码:
// javascriptcn.com 代码示例 const routes = [ // ... { method: 'GET', path: '/{path*}', handler: (request, reply) => { const context = { insertCss: styles => styles._insertCss(), pathname: request.url.path, query: request.query, userAgent: request.headers['user-agent'], }; const app = ( <StaticRouter basename="/" location={request.url.path} context={context} > <App /> </StaticRouter> ); const html = renderToString(app); const helmet = Helmet.renderStatic(); const css = context.css.join('\n'); // Return the page return reply(renderPage({ html, css, helmet })); }, }, { method: 'GET', path: '/static/{path*}', handler: { directory: { path: './build/static', }, }, }, { method: 'GET', path: '/index.html', // 这个路由必须提前定义 handler: (request, reply) => { return reply.file(path.resolve(__dirname, '../build/index.html')); }, }, ];
用户请求与服务器请求的指定
在中,我们需要明确地传递当前的请求路径location,以便在后面的处理中以正确的方式解析响应。
// javascriptcn.com 代码示例 const app = ( <StaticRouter location={req.url} context={context} > <Provider store={store}> <App /> </Provider> </StaticRouter> );
在加载数据时,我们需要进行判断,以确定是在客户端(通过AJAX)还是在服务器端(直接获取数据)上加载数据。可以通过判断现有的请求来解决这个问题。
// javascriptcn.com 代码示例 const fetchData = (dispatch, location) => { const match = getRoutesMatch(location, routes); const { fetchData } = match.route; const { params } = match; const request = isBrowser() ? apiClient() : apiServer(request); if (!fetchData) { return; } return fetchData({ request, params, dispatch }); };
样式管理
如果您使用了Hapi主题,则需要在服务器上编写代码,以便样式可以输出到Hapi主题中。在这里,我们采用的是样式管理器Glamor。在React组件中我们可以采用Glamor编写样式。它的主要优点是样式映射到属性。因此,在中收集的样式可以轻松应用于组件中的样式。
以下是一个样式管理部分的示例代码:
// javascriptcn.com 代码示例 import { ServerStyleSheet } from 'glamor/server'; import glamorous from 'glamorous'; import css from './App.css'; const renderPage = async (ctx, next) => { const sheet = new ServerStyleSheet() let html; try { const { pathname } = parseUrl(ctx.url); const context = {}; const data = { messages: ['foo', 'bar', 'baz'], }; const app = ( <App pathname={pathname} context={context} data={data} history={history} classes={{ glamorous, css }} /> ); html = renderToString(sheet.collectStyles(app)); } finally { sheet.seal(); } const helmet = Helmet.renderStatic(); let css = sheet.getStyleTags(); return ctx.respond({ body: layout({ html, helmet, css, }), }); };
Webpack配置
在Hapi应用中使用Webpack打包React应用有一些变化。首先,需要在Webpack配置中增加target: node选项:
module.exports = { // ... target: 'node', // ... };
然后,在config/server.js中,我们要求使用webpack-hot-server插件。从创建webpack实例,并将实例传递给.
// javascriptcn.com 代码示例 import ServerRenderer from './lib/ServerRenderer'; import ServerCompiler from './lib/ServerCompiler'; import { PRODUCTION, DEVELOPMENT } from './constants'; const createWebpackCompiler = (config, log) => { const compiler = webpack(config); compiler.plugin('done', stats => { const messages = formatWebpackMessages(stats.toJson({}, true)); if (messages.errors.length) { log.error(messages.errors.join('\n\n'), 'Webpack'); } else if (messages.warnings.length) { log.warn(messages.warnings.join('\n\n'), 'Webpack'); } else { log.info('Compiled successfully!', 'Webpack'); } }); return compiler; }; const isDevelopment = process.env.NODE_ENV === DEVELOPMENT; const webpackCompiler = createWebpackCompiler( webpackConfig, logger, ); const serverRenderer = new ServerRenderer( webpackCompiler, isDevelopment ? require('../public/assets/stats.json') : null, ); const app = new Hapi.Server(); new ServerCompiler({ webpackCompiler, devMiddlewares, hapiApp: app, }).start();
安装生产环境附加组件
在生产应用中,我们需要安装Hapi官方推荐的组件来确保应用在生产环境中正确运行。这些框架和组件包括:joi (数据验证),hapi-auth-jwt2 (身份验证),good (日志记录),hapi-alive (简单地检查Hapi服务器命令中的URL活动),hapi-geo-locate (IP位置解析) 等。
建议在本地、测试和生产环境中都使用这些附加组件,因为这可以使调试过程更加容易。
总结
在本篇文章中,我们介绍了如何使用Hapi框架构建React SSR应用,并解决了在实践过程中可能遇到的一些问题,如路由配置、静态文件路径问题、用户请求与服务器请求的指定、样式管理、Webpack配置以及安装生产环境附加组件等。希望这篇文章对于初学者和有React SSR的开发者都有所帮助,让您能够更好地应用React SSR技术。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6527c6117d4982a6eba5d1a0