Nuxt中如何做页面html缓存

4,716 阅读3分钟

Nuxt是一款基于Vue的服务端渲染SSR框架

在Nuxt框架的API中,有一个叫 serverMiddleware 的服务端中间件,我们可以利用它在返回首屏html前做一些缓存的处理

在这之前我们需要了解一个叫LRU的算法,LRU是一种缓存淘汰算法,用链表存储数据,最新插入的数据会排在链表头部,已缓存的数据收到访问也会被移到链表的头部,链表有长度限制,满了的时候排在尾部的数组将被丢弃。

想详细了解这个算法的可以看:juejin.cn/post/684490…

关于前端缓存,大家可以看看:juejin.cn/post/684490…

进入正题,实现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;
}

大功告成!