fastify-prerender-plugin
Version:
Prerender SPA pages for bots
104 lines (100 loc) • 3.01 kB
JavaScript
import Fs from 'node:fs';
import Path from 'node:path';
import { fastifyPlugin } from 'fastify-plugin';
import { isbot } from 'isbot';
import sanitize from 'sanitize-filename';
import tmp from 'tmp';
import { chromium } from '@playwright/test';
async function requestFromBrowser(url) {
const browser = await chromium.launch({
args: [
"--disable-dev-shm-usage",
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
"--disable-extensions",
"--disable-sync",
"--disable-translate",
"--single-process",
// Run in a single process
"--no-zygote",
// Disable the zygote process for reduced memory usage
"--disable-gpu"
// Disable GPU hardware acceleration
],
headless: true
});
const page = await browser.newPage({
userAgent: "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
});
await page.goto(url);
let html;
try {
html = await page.content();
} catch (error) {
console.error(error);
return "";
}
await browser.close();
return html;
}
const prerenderPlugin = fastifyPlugin(
(app, options, done) => {
const tmpobj = tmp.dirSync();
app.addHook("onRequest", async (request, reply) => {
const requestFromBot = isbot(request.headers["user-agent"]);
if (!requestFromBot || request.method !== "GET") {
return;
}
request.log.info({ requestFromBot });
let matches = false;
for (const url2 of options.urls) {
if (typeof url2 === "string") {
if (url2 === request.url) {
matches = true;
break;
}
} else {
if (url2.test(request.url)) {
matches = true;
break;
}
}
}
if (!matches) {
return;
}
const url = `http://${options.host}:${options.port}${request.url}`;
const filepath = Path.join(
options.tmpPath ?? tmpobj.name,
sanitize(`${url}.html`)
);
if (Fs.existsSync(filepath)) {
const fileStat = Fs.statSync(filepath);
const fileAgeInMinutes = (Date.now() - fileStat.mtime.getTime()) / 1e3 / 60;
if (fileAgeInMinutes <= 5) {
return reply.status(200).type("text/html").send(Fs.readFileSync(filepath));
}
Fs.rmSync(filepath);
}
request.log.info(`request-from-browser: ${url}`);
const html = await requestFromBrowser(url).catch((error) => {
console.error(error);
}) ?? "";
try {
Fs.writeFileSync(filepath, html);
} catch (error) {
request.log.error(`Couldn't write cache in ${filepath}`);
}
reply.status(200).type("text/html").send(html);
});
done();
},
{
fastify: "^5.x",
name: "fastify-prerender"
}
);
export { prerenderPlugin, requestFromBrowser };