UNPKG

next-boost

Version:

Add a cache layer for next.js SSR pages. Use stale-while-revalidate to boost the performance.

105 lines (104 loc) 4.69 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const next_boost_hdc_adapter_1 = __importDefault(require("next-boost-hdc-adapter")); const zlib_1 = require("zlib"); const cache_manager_1 = require("./cache-manager"); const renderer_1 = __importDefault(require("./renderer")); const utils_1 = require("./utils"); function matchRule(conf, req) { const err = ['GET', 'HEAD'].indexOf(req.method) === -1; if (err) return { matched: false, ttl: -1 }; if (typeof conf.rules === 'function') { const ttl = conf.rules(req); if (ttl) { return { matched: true, ttl }; } } else { for (const rule of conf.rules) { if (req.url && new RegExp(rule.regex).test(req.url)) { return { matched: true, ttl: rule.ttl }; } } } return { matched: false, ttl: 0 }; } function toBuffer(o) { return Buffer.from(JSON.stringify(o)); } // mutex lock to prevent same page rendered more than once const SYNC_LOCK = new Set(); const wrap = (cache, conf, renderer, plainHandler) => { return (req, res) => __awaiter(void 0, void 0, void 0, function* () { req.url = (0, utils_1.filterUrl)(req.url, conf.paramFilter); const key = conf.cacheKey ? conf.cacheKey(req) : req.url; const { matched, ttl } = matchRule(conf, req); if (!matched) return plainHandler(req, res); const start = process.hrtime(); const fc = req.headers['x-cache-status'] === 'update'; // forced const { status, stop } = yield (0, cache_manager_1.serveCache)(cache, SYNC_LOCK, key, fc, res); if (stop) return !conf.quiet && (0, utils_1.log)(start, status, req.url); // log the time took for staled if (status === 'stale') !conf.quiet && (0, utils_1.log)(start, status, req.url); SYNC_LOCK.add(key); const args = { path: req.url, headers: req.headers, method: req.method }; const rv = yield renderer.render(args); // rv.body is a Buffer in JSON format: { type: 'Buffer', data: [...] } const body = Buffer.from(rv.body); // stale means already served from cache with old ver, just update the cache if (status !== 'stale') (0, utils_1.serve)(res, rv); // stale will print 2 lines, first 'stale', second 'update' !conf.quiet && (0, utils_1.log)(start, status === 'stale' ? 'update' : status, req.url); if (rv.statusCode === 200 && body.length > 0) { // save gzipped data const buf = (0, utils_1.isZipped)(rv.headers) ? body : (0, zlib_1.gzipSync)(body); yield cache.set('body:' + key, buf, ttl); yield cache.set('header:' + key, toBuffer(rv.headers), ttl); } else if (status === 'force') { // updating but empty result yield cache.del('body:' + key); yield cache.del('header:' + key); } SYNC_LOCK.delete(key); }); }; function CachedHandler(args, options) { return __awaiter(this, void 0, void 0, function* () { console.log('> Preparing cached handler'); // merge config const conf = (0, utils_1.mergeConfig)(options); // the cache const cache = conf.cacheAdapter || next_boost_hdc_adapter_1.default.init(); const renderer = (0, renderer_1.default)(); yield renderer.init(args); const plain = yield require(args.script).default(args); // init the child process for revalidate and cache purge return { handler: wrap(cache, conf, renderer, plain), cache, close: () => { next_boost_hdc_adapter_1.default.shutdown(); renderer.kill(); }, }; }); } exports.default = CachedHandler;