新博客是使用 React 进行客户端渲染的,考虑到 SEO
的问题,决定改用服务端渲染来进行 SEO 优化
。
React 发展至今,其服务端渲染已经有很成熟的方案,比如 Next ,但是对于那些比较简单的 React 应用,其实不用框架反而会更简单一些。
服务端渲染的原理其实就是在 Node 层调用 React 提供的 renderToString
生成 html 作为响应体返回客户端
// server.js
import express from 'express';
const app = express();
import { renderToString } from 'react-dom/server';
app.get('/', (req, res) => {
const content = renderToString(
<div>
Hello World!
</div>
);
res.send(content);
});
对于需要从服务端异步获取数据的组件,只要对服务端暴露一个获取数据的方法即可
// UserList.js
import { fetchUsers } from "../actions/index";
class UsersList extends Component {
componentDidMount() {
this.props.fetchUsers();
}
renderUsers() {
return this.props.users.map(user => {
return <li key={user.id}>{user.name}</li>
})
}
render() {
return (
<div>
<ul>
{this.renderUsers()}
</ul>
</div>
)
}
}
function mapStateToProps(state) {
return {
users: state.users
}
}
function loadData(store) {
return store.dispatch(fetchUsers());
}
export default {
component: connect(mapStateToProps, {
fetchUsers
})(UsersList),
loadData
};
// server.js
app.get('/', (req, res) => {
const promises = matchRoutes(Routes, req.path).map(({ route }) => {
const {
loadData
} = route; // 检测是否需要 loadData
return loadData ? loadData(store) : null;
}).map(promise => {
if(promise) {
return new Promise((resolve) => {
promise.then(resolve).catch(resolve);
})
}
});
Promise.all(promises).then(() => {
const content = renderToString(
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<div>
{renderRoutes(Routes)}
</div>
</StaticRouter>
</Provider>
);
res.send(content);
});
});
之前学习服务端渲染的时候写过一个 demo 实现了 Express + React + Redux + React-Router
的 服务端渲染,如果想自己实现 ssr 可以作为参考 React-SSR。
最后本着要顺便学习 Next 的目的,还是决定使用 Next 来实现服务端渲染。
第一件事当然是安装 Next
$ npm next @zeit/next-css @zeit/next-less next-images --save
除了 Next 还顺便安装了用于处理样式和图片的插件,基本上做一个应用离不开这些依赖。
对于全新的项目,Next 的安装会非常顺利,然而在我的实践中,遇到了一个坑。
因为项目之前是使用 webpack 作为打包工具,所以已经安装了 webpack
、webpack-cli
还有各种各样 loader
,但是 Next 本地也内置了 webpack 「全家桶」,所以在安装完 Next 之后,项目中就存在两个版本的 webpack
编译的时候将会有一个又一个错等着你,所以对于已有项目的升级,建议安装 next 之前,遵循以下步骤
- 删掉
package.json
里跟 webpack 有关的依赖, - 删掉
package-lock.json
- 删掉
node_modules
npm install
一次- 安装 Next 相关依赖
Next 的 文档 已经提供了非常详细的使用说明,开发上和 React
基本没什么区别,只是在 Next 并不需要声明 React
// import React from 'react';
export default () => <div>Welcome to next.js!</div>
当在命令行运行 Next 的时候
$ next
Next 会根据默认目录 pages
中的 js
文件生成对应页面路由服务
pages/index.js
对应 {domain}/
pages/about.js
对应 {domain}/about
对于需要从服务端异步获取数据的组件,只要为组件实现一个 getInitialProps
的静态方法即可
class PostListPage extends Component {
static async getInitialProps () {
const posts = await getPostsList();
return {
posts
}
}
constructor(props) {
super(props);
}
render() {
const {
posts
} = this.props;
return posts.map(post => {
// ...
})
)
}
}
同时 Next 提供了客户端路由支持,因此你的应用不再需要 React-Router
import Link from 'next/link';
const Navigator = () =>
<nav>
<Link href="/"><a>index</a></Link>
<Link href="/about"><a>about</a></Link>
</nav>;
export default Navigator;
在根目录创建 next.config.js
修改 Next 的默认配置,或者进行插件增强,比如 Next 默认只支持 CSS in JS
,如果要支持 import
css 文件或者使用 less 等预编译器,就需要用到插件
const withLess = require('@zeit/next-less');
module.exports = withLess();
再比如 Next 默认不支持通过 import
引入图片,这时候就需要 next-images
const withLess = require('@zeit/next-less');
const withImages = require('next-images');
module.exports = withImages(withLess());
你也可以自己修改 Next 内部的 webpack 配置
const withLess = require('@zeit/next-less');
const withImages = require('next-images');
module.exports = withImages(withLess({
webpack (config) {
// 对 config 进行修改
return config;
}));
参考我在开发过程遇到的一个 场景
Next 只提供简单的客户端路由,为了程序扩展性更强,应该搭配专业的 Node 服务端框架使用,目前项目是使用 Egg 作为服务端,需要将 Next 和 Egg 进行融合。
方案参考 这个 即可
运行 next build
即可
博客升级到 Next 一共用了不到一天的时间,可见 Next 还是挺强大的,当你看到这篇文章的时候,看到的已经是一个服务端渲染的 JavaScript 同构应用了。