Nuxt是一款基于Vue的服务端渲染SSR框架
在Nuxt框架的API中,有一个叫 serverMiddleware 的服务端中间件,我们可以利用它在返回首屏html前做一些缓存的处理
在这之前我们需要了解一个叫LRU的算法,LRU是一种缓存淘汰算法,用链表存储数据,最新插入的数据会排在链表头部,已缓存的数据收到访问也会被移到链表的头部,链表有长度限制,满了的时候排在尾部的数组将被丢弃。
进入正题,实现html缓存我们需要两个包,一个是lru-cache,一个是etag
(lru-cache是别人封装好的一个lru存储类,etag是用来为实体内容产生一个strong etag)
建一个pageCache.js,引入这两个包
import LRU from 'lru-cache';
import etag from 'etag';
const cacheStore = new LRU({
max: 10000, // 设置最大的缓存个数
maxAge: 5 * 60 * 1000 // 5分钟
});
复制代码
我们可以根据需要调整max和maxAge,来控制LRU缓存
接着我们来自定义一个serverMiddleware
import LRU from 'lru-cache';
import etag from 'etag';
const cacheStore = new LRU({
max: 10000, // 设置最大的缓存个数
maxAge: 5 * 60 * 1000 // 5分钟
});
export default function(req, res, next) {
const isDev = process.env.NODE_ENV === 'development'
// 开发环境为了方便开发,就不走缓存
if (isDev) {
return next()
}
// 此次我们只针对html做缓存
if (req.headers.accept &&
(req.headers.accept.indexOf('text/html') === -1
&& req.headers.accept.indexOf('application/xhtml+xml') === -1
&& req.headers.accept.indexOf('application/xml') === -1)
) {
return next()
}
// 用页面url的pathname作为LRU缓存的key值
let key = req._parsedOriginalUrl.pathname
// 获取LRU中的缓存
const { etag: curEtag, value } = cacheStore.get(key) || {}
if (value) {
// 如果命中缓存,则看是否命中协商缓存,是则直接返回304,不是则返回200和数据
if (curEtag === req.hedaers['if-none-match']) {
res.writeHead(304)
return res.end()
} else {
res.writeHead(200, {
'Content-Type': 'text/html;charset=utf-8',
'Cache-Control': 'private, max-age=60',
'Etag': curEtag
})
}
} else {
// 如果缓存没命中,则返回请求的内容
// 缓存原先的res.end
res.original_end = res.end
// 重写res.end方法,nuxt服务器响应时会调用res.end
res.end = function(data) {
if (res.statusCode === 200) {
// 将该页面请求html内容存进LRU
// 第三个参数缓存时间传undefined则走起初cacheStore定义时的5分钟
cacheStore.set(key, { etag: etag(data), value: data }, undefined)
}
// html设置客户端强缓存60s
res.setHeader('Cache-Control', 'private, max-age=60')
// 最终返回请求的内容
return res.original_end(data, 'utf-8')
}
retun next()
}
}
复制代码
然后我们在nuxt.config.js内配上这个中间件就可以了
serverMiddleware: [ '~/../pageCache']
复制代码
看起来好像OK了
ちょっと待って
我们页面url不可避免会有参数,而且参数可能会影响页面的内容,如果我们只是用页面url的pathname做缓存的key的话,会导致不同参数的url访问到的内容都命中了同一个缓存,那该怎么办呢?很简单,我们用 pathname+query 作为缓存的key就可以了
import { parse, stringify } from 'querystring';
let key = req._parsedOriginalUrl.pathname;
//是否是需要缓存的x页面
if(req._parsedOriginalUrl.pathname.toLowerCase().indexOf('/x') >= 0){
const query = parse(req._parsedUrl.query)
const queryStr = stringify({ ...query })
key = key + '?' + queryStr;
}
复制代码
大功告成!