There are 3 different ways to use cache in Nuxt:
- Axios (API) Level Cache
- Component/Page Level Cache
- Route Level Cache
With an adapter in the Axios, we can easily add API Level Cache.
This is the code as a Nuxt module
import Redis from "ioredis";
export default async function () {
const client = new Redis("127.0.0.1:6379");
this.nuxt.hook("vue-renderer:ssr:prepareContext", (ssrContext) => {
ssrContext.$axiosCache = client;
});
}
And in a server plugin:
import LRUCache from "lru-cache";
import buildURL from "axios/lib/helpers/buildURL";
export default function ({$axios, ssrContext}) {
const defaults = $axios.defaults;
defaults.adapter = cacheAdapterEnhancer(defaults.adapter, {
enabledByDefault: false,
cacheFlag: "useCache",
defaultCache: ssrContext.$axiosCache,
});
}
function isCacheLike(cache) {
return !!(
cache.set &&
cache.get &&
cache.del &&
typeof cache.get === "function" &&
typeof cache.set === "function" &&
typeof cache.del === "function"
);
}
function buildSortedURL(...args) {
const builtURL = buildURL(...args);
const [urlPath, queryString] = builtURL.split("?");
if (queryString) {
const paramsPair = queryString.split("&");
return `${urlPath}?${paramsPair.sort().join("&")}`;
}
return builtURL;
}
const FIVE_MINUTES = 1000 * 60 * 5;
const CAPACITY = 100;
function cacheAdapterEnhancer(adapter, options = {}) {
const {
enabledByDefault = true,
cacheFlag = "cache",
defaultCache = new LRUCache({maxAge: FIVE_MINUTES, max: CAPACITY}),
} = options;
return async (config) => {
const {url, method, params, paramsSerializer, forceUpdate} = config;
const useCache = config[cacheFlag] ? config[cacheFlag] : enabledByDefault;
if (method === "get" && useCache) {
// if had provide a specified cache, then use it instead
const cache = isCacheLike(useCache) ? useCache : defaultCache;
// build the index according to the url and params
const index = buildSortedURL(url, params, paramsSerializer);
let responsePromise = await cache.get(index);
if (!responsePromise || forceUpdate) {
responsePromise = (async () => {
try {
return await adapter(config);
} catch (reason) {
cache.del(index);
throw reason;
}
})();
// put the promise for the non-transformed response into cache as a placeholder
responsePromise.then(({data}) => cache.set(index, JSON.stringify(data)));
return responsePromise;
}
/* istanbul ignore next */
if (process.env.LOGGER_LEVEL === "info") {
// eslint-disable-next-line no-console
console.info(`[axios-extensions] request cached by cache adapter --> url: ${index}`);
}
return Promise.resolve({data: JSON.parse(responsePromise)});
}
return adapter(config);
};
}
With the Vue SSR package, we can simply use page/component cache.
This is the code as a Nuxt module
import Redis from "ioredis";
export default function nuxtComponentCache(options) {
if (this.options.render.ssr === false) {
// SSR Disabled
return;
}
// Create empty bundleRenderer object if not defined
if (
typeof this.options.render.bundleRenderer !== "object" ||
this.options.render.bundleRenderer === null
) {
this.options.render.bundleRenderer = {};
}
// Disable if cache explicitly provided in project
if (this.options.render.bundleRenderer.cache) {
return;
}
const redis = new Redis("127.0.0.1:6379");
this.options.render.bundleRenderer.cache = {
get: (key, cb) => {
redis.get(key).then(deserialize).then(cb);
},
has: (key, cb) => {
redis.exists(key).then(cb);
},
set: (key, val) => {
redis.set(key, serialize(val));
},
};
}
function serialize(result) {
return JSON.stringify(result, (key, value) => {
if (typeof value === "object" && value instanceof Set) {
return {_t: "set", _v: [...value]};
}
if (typeof value === "function") {
return {_t: "func", _v: value.toString()};
}
return value;
});
}
function deserialize(jsonString) {
return JSON.parse(jsonString, (key, value) => {
if (value && value._v) {
if (value._t === "set") {
return new Set(value._v);
}
if (value._t === "func") {
// eslint-disable-next-line no-new-func
return new Function(`return ${value._v}`);
}
}
return value;
});
}
Thanks to the Nuxt modules and hooks, we can create route-level caching.
With a module, we can save routes response:
import Redis from "ioredis";
export default function () {
const redis = new Redis("127.0.0.1:6379");
this.nuxt.hook("render:routeDone", async (url, result) => {
const hasKey = await redis.exists(url);
if (!hasKey) {
redis.set(url, result.html, "EX", 60);
}
});
}
And with a server middle we can serve the cached response:
import Redis from "ioredis";
export default async function (req, res, next) {
const cacheable = isCacheable(req);
if (cacheable) {
const redis = new Redis("127.0.0.1:6379");
const hit = await redis.get(req.url);
if (hit) {
redis.quit();
res.setHeader("ch-cache-hit", "HIT");
return res.end(hit);
}
}
res.setHeader("ch-cache-hit", "BYPASS");
next();
}
function isCacheable(req) {
return true;
}