UNPKG

@fortedigital/nextjs-cache-handler

Version:
289 lines (286 loc) 8.57 kB
// src/instrumentation/register-initial-cache.ts import { promises as fsPromises } from "fs"; import path from "path"; import { PRERENDER_MANIFEST, SERVER_DIRECTORY } from "next/constants"; import { CACHE_ONE_YEAR } from "next/dist/lib/constants"; // src/helpers/getTagsFromHeaders.ts function getTagsFromHeaders(headers) { const tagsHeader = headers["x-next-cache-tags"]; if (Array.isArray(tagsHeader)) { return tagsHeader; } if (typeof tagsHeader === "string") { return tagsHeader.split(","); } return []; } // src/instrumentation/register-initial-cache.ts var PRERENDER_MANIFEST_VERSION = 4; async function registerInitialCache(CacheHandler, options = {}) { const debug = typeof process.env.NEXT_PRIVATE_DEBUG_CACHE !== "undefined"; const nextJsPath = path.join(process.cwd(), ".next"); const prerenderManifestPath = path.join(nextJsPath, PRERENDER_MANIFEST); const serverDistDir = path.join(nextJsPath, SERVER_DIRECTORY); const fetchCacheDir = path.join(nextJsPath, "cache", "fetch-cache"); const populateFetch = options.fetch ?? true; const populatePages = options.pages ?? true; const populateRoutes = options.routes ?? true; let prerenderManifest; try { const prerenderManifestData = await fsPromises.readFile( prerenderManifestPath, "utf-8" ); prerenderManifest = JSON.parse(prerenderManifestData); if (prerenderManifest.version !== PRERENDER_MANIFEST_VERSION) { throw new Error( `Invalid prerender manifest version. Expected version ${PRERENDER_MANIFEST_VERSION}. Please check if the Next.js version is compatible with the CacheHandler version.` ); } } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to read prerender manifest", `Error: ${error}` ); } return; } const context = { serverDistDir, dev: process.env.NODE_ENV === "development" }; let cacheHandler; try { cacheHandler = new CacheHandler( context ); } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to create CacheHandler instance", `Error: ${error}` ); } return; } async function setRouteCache(cachePath, router, revalidate) { const pathToRouteFiles = path.join(serverDistDir, router, cachePath); let lastModified; try { const stats = await fsPromises.stat(`${pathToRouteFiles}.body`); lastModified = stats.mtimeMs; } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to read route body file", `Error: ${error}` ); } return; } let body; let meta; try { [body, meta] = await Promise.all([ fsPromises.readFile(`${pathToRouteFiles}.body`), fsPromises.readFile(`${pathToRouteFiles}.meta`, "utf-8").then((data) => JSON.parse(data)) ]); if (!(meta.headers && meta.status)) { throw new Error("Invalid route metadata. Missing headers or status."); } } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to read route body or metadata file, or parse metadata", `Error: ${error}` ); } return; } try { const value = { kind: "APP_ROUTE", body, headers: meta.headers, status: meta.status }; await cacheHandler.set(cachePath, value, { revalidate, internal_lastModified: lastModified, tags: getTagsFromHeaders(meta.headers) }); } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to set route cache. Please check if the CacheHandler is configured correctly", `Error: ${error}` ); } return; } } async function setPageCache(cachePath, router, revalidate) { const isAppRouter = router === "app"; if (isAppRouter && cachePath === "/") { cachePath = "/index"; } const pathToRouteFiles = path.join(serverDistDir, router, cachePath); let lastModified; try { const stats = await fsPromises.stat(`${pathToRouteFiles}.html`); lastModified = stats.mtimeMs; } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to read page html file", `Error: ${error}` ); } return; } let html; let pageData; let meta; try { [html, pageData, meta] = await Promise.all([ fsPromises.readFile(`${pathToRouteFiles}.html`, "utf-8"), fsPromises.readFile( `${pathToRouteFiles}.${isAppRouter ? "rsc" : "json"}`, "utf-8" ).then((data) => isAppRouter ? data : JSON.parse(data)), isAppRouter ? fsPromises.readFile(`${pathToRouteFiles}.meta`, "utf-8").then((data) => JSON.parse(data)) : void 0 ]); } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to read page html, page data, or metadata file, or parse metadata", `Error: ${error}` ); } return; } try { const value = { kind: isAppRouter ? "APP_PAGE" : "PAGES", html, pageData, postponed: meta?.postponed, headers: meta?.headers, status: meta?.status, rscData: void 0, segmentData: void 0 }; await cacheHandler.set(cachePath, value, { revalidate, internal_lastModified: lastModified }); } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to set page cache. Please check if the CacheHandler is configured correctly", `Error: ${error}` ); } return; } } for (const [ cachePath, { dataRoute, initialRevalidateSeconds } ] of Object.entries(prerenderManifest.routes)) { if (populatePages && dataRoute?.endsWith(".json")) { await setPageCache(cachePath, "pages", initialRevalidateSeconds); } else if (populatePages && dataRoute?.endsWith(".rsc")) { await setPageCache(cachePath, "app", initialRevalidateSeconds); } else if (populateRoutes && dataRoute === null) { await setRouteCache(cachePath, "app", initialRevalidateSeconds); } } if (!populateFetch) { return; } let fetchFiles; try { fetchFiles = await fsPromises.readdir(fetchCacheDir); } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to read cache/fetch-cache directory", `Error: ${error}` ); } return; } for (const fetchCacheKey of fetchFiles) { const filePath = path.join(fetchCacheDir, fetchCacheKey); let lastModified; try { const stats = await fsPromises.stat(filePath); lastModified = stats.mtimeMs; } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to read fetch cache file", `Error: ${error}` ); } return; } let fetchCache; try { fetchCache = await fsPromises.readFile(filePath, "utf-8").then((data) => JSON.parse(data)); } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to parse fetch cache file", `Error: ${error}` ); } return; } const revalidateValue = fetchCache.revalidate; const revalidate = revalidateValue === CACHE_ONE_YEAR ? false : revalidateValue; try { await cacheHandler.set(fetchCacheKey, fetchCache, { revalidate, internal_lastModified: lastModified, tags: fetchCache.tags }); } catch (error) { if (debug) { console.warn( "[CacheHandler] [%s] %s %s", "registerInitialCache", "Failed to set fetch cache. Please check if the CacheHandler is configured correctly", `Error: ${error}` ); } } } } export { registerInitialCache };