技术博客 SSR 技术选型
由于采用了前后端分离的技术方案,导致我的博客完全没有了 SEO(虽然也没啥阅读量 O_o),但还是想要从这个点切入折腾优化一下。
前端是基于 React 技术栈的,那么最热门的 SSR 框架肯定是 next.js 了。初步刷了一下文档,由于之前没有考虑过 SSR,现在迁移到 next.js 需要对代码有不小的改动(参考文档:Migrating from Create React App),主要有以下几点:
-
更新 package.json
将 start/build/dev 脚本从 react-scripts 替换为 next,并安装 next 相关的依赖。 -
修改静态资源的输出
-
将基于 React-router-dom 的路由方案,转换到基于文件路径的路由方案
这一点是我觉得最难受的,本来可以按个人喜好业务逻辑组织路由/组件的,现在非得按 next 的规则放到 page 目录下。 -
处理 client 端的一些 API
window, localStorage 这些都要在使用前进行边界条件判断。
其中第 3 点的改动是我最不能接受的,不想让我自己的前端架构受到 next 的约束。
那就去研究一番,其实本质上 SSR 就是在 server-side 直接生成 HTML 发给 client,免去 JavaScript 在 client 执行的过程,这里的核心的就是要求我们的软件是同构的(isomorphic),即编译后的 JS 代码能同时在 Node 环境和 Browser 环境运行。
开工!
- 先是修改程序入口文件 index.tsx,这一步也是实现同构的重要步骤
// ReactDOM.render(
// <React.StrictMode>
// <App />
// </React.StrictMode>,
// document.getElementById('root')
// );
ReactDOM.hydrate(<App />, document.getElementById('root'));
- 然后修改路由相关代码,将需要 Dom 的 BrowserRouter 替换为能同时运行在 Node 环境的 Router:
function App() {
const history = isBrowser
? createBrowserHistory()
: createMemoryHistory({initialEntries: ['/']});
return (
<Router history={history}>
{/* <BrowserRouter> */}
<Layout className="App">
<Header />
<Body />
<Footer />
</Layout>
</Router>
// </BrowserRouter>
);
}
- 搭建 Express 服务器
基本思路为配置好监听的路由,将后端 Node 环境运行后的结果返回给客户端,由客户端的 ReactDOM.hydrate 处理后端的同构代码。
// ...
const app = express();
app.get('/', (req, res) => {
const app = ReactDOMServer.renderToString(<App />);
const indexFile = path.resolve('./build/index.html');
fs.readFile(indexFile, 'utf8', (err, data) => {
if (err) {
return res.status(500).send('something wrong');
}
return res.send(
data.replace('<div id="root"></div>', `<div id="root">${app}</div>`)
);
});
});
app.use(express.static('./build'));
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
- 配置开发调试环境
配置 webpack、Babel 以及 npm 脚本,本以为这一步是最不重要的,结果却被打了脸。不是 babel-loader 版本对不上,就是 babel preset 有问题,好不容易处理完了错误,能跑起来并在浏览器中打开 server rendered 页面,还没来得及开心就发现 CSS 的解析有问题。
好吧,只能说 webpack 的配置是博大精深,认栽了,还是滚回去用 nextjs 框架吧😭