情况说明
众所周知,在 SSR 服务中,渲染阶段是非常消耗渲染服务器资源的。
比如网站的首页,会经常被访问到,如果每一次请求渲染服务器都要执行一遍渲染操作,不仅速度上要慢很多,而且在高并发状态下会出现性能瓶颈。
这时,如果我们在页面第一次渲染完成后,就将这次的渲染结果缓存下来。当再次请求时直接把渲染好的渲染结果返回,就省了渲染、查询等一系列的操作。无疑对程序的性能和可用性上都是一次大的提升。
那么在 Nuxt.js 中,我们该怎么实现这个页面级的缓存呢?
Module
模块是 Nuxt.js 扩展,可以扩展其核心功能并添加无限的集成。
在使用 Nuxt 开发应用程序时,您很快就会发现框架的核心功能还不够。 Nuxt 可以使用配置选项和插件进行扩展,但是在多个项目中维护这些自定义是繁琐、重复和耗时的。 另一方面,开箱即用支持每个项目的需求将使 Nuxt 非常复杂且难以使用。 这就是 Nuxt 提供更高阶模块系统的原因,可以轻松扩展核心。 模块只是在引导 Nuxt 时按顺序调用的函数。 框架在加载之前等待每个模块完成。 如此,模块几乎可以自定义 Nuxt 的任何地方。 我们可以使用功能强大的 Hookable Nuxt.js 系统来完成特定事件的任务。
官网链接:zh.nuxtjs.org/guide/modul…
简而言之,我们可以通过配置一个 Module,来扩展 Nuxt。通过 Nuxt 的 Hook,在渲染时、渲染前改变默认的行为。
Nuxt render 阶段的 Hook: nuxtjs.org/api/interna…
我们可以在 render:before 这个 hook 的 callback 里有 renderer 这个渲染器,那么我们可以试着 重写本次的渲染器。
重写渲染器
this.nuxt.hook("render:before", (renderer, options) => {
// 首先拿到原来的渲染方法renderRoute
const renderRoute = renderer.renderRoute.bind(renderer);
// 重写renderer.renderRoute
renderer.renderRoute = async function(route, context) {
// isCacheFriendly 用来判断当前的route是否和moduleOptions.matches中配置的一致
// moduleOptions是nuxt module配置中传过来的参数
// 没有匹配的情况下,返回原来的renderRoute
if (!isCacheFriendly(route, moduleOptions.matches)) {
return renderRoute(route, context);
}
// 返回值
let value;
try {
// getAsync 是 取Redis的值
value = await getAsync(route);
} catch (err) {
// 如果出错了,返回原来的renderRoute
console.log(err);
return renderRoute(route, context);
}
if (!value) {
// 没有找到值,返回原来的renderRoute
return renderRoute(route, context);
} else {
// 标记命中, 写在context中,在后续hook中可以取到
context.req.hitCache = true;
// 从cache中获取,封装成Promise
console.log("return from cache");
return new Promise(function(resolve, reject) {
// 返回取到的值
resolve(deserialize(value));
});
}
};
});
那么在哪里存这个页面的值呢?
存渲染完成的页面
可以在 render:route 这个 hook 中来做处理。
// add cache hit header
this.nuxt.hook("render:route", (url, result, context) => {
// 如果hitCache了,那么就标记一个响应头。
// 如果没有,那么就缓存一下。
if (context.req.hitCache) {
context.res.setHeader("x-page-cache", "hit");
} else if (isCacheFriendly(url, moduleOptions.matches)) {
client.set(url, serialize(result), "EX", moduleOptions.expireTime ? moduleOptions.expireTime : 1000);
}
});
完整代码
完整代码请见: github.com/tangzhongzh… 使用方法请见 GITHUB README
更多:完整代码还加入了,版本管理,登录状态判断等。甚至还可以将渲染好的页面放到 cdn 中。