Created
November 19, 2025 03:50
-
-
Save H01001000/87c1d98a5e66410997c276094b1b7dc2 to your computer and use it in GitHub Desktop.
Hack script for using fortedigital/nextjs-cache-handler for Next.js's cachehandlers (for cache component / "use cache")
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { | |
| CacheHandlerValue, | |
| Handler, | |
| LifespanParameters, | |
| } from "@fortedigital/nextjs-cache-handler/cache-handler.types"; | |
| import type { | |
| CacheEntry, | |
| CacheHandler as CacheHandlerType, | |
| } from "next/dist/server/lib/cache-handlers/types"; | |
| import CacheHandler from "./cache-handler.ts"; | |
| // Based on https://github.com/vercel/next.js/blob/83ae64fe773453dbcf1583e5303494413b8449d7/packages/next/src/server/lib/cache-handlers/default.ts#L38C1-L206C2 | |
| export async function createDefaultCacheHandler( | |
| maxSize: number, | |
| ): Promise<CacheHandlerType> { | |
| // If the max size is 0, return a cache handler that doesn't cache anything, | |
| // this avoids an unnecessary LRUCache instance and potential memory | |
| // allocation. | |
| if (maxSize === 0) { | |
| return { | |
| get: () => Promise.resolve(undefined), | |
| set: () => Promise.resolve(), | |
| refreshTags: () => Promise.resolve(), | |
| getExpiration: () => Promise.resolve(0), | |
| updateTags: () => Promise.resolve(), | |
| }; | |
| } | |
| await new CacheHandler({}).get(""); | |
| const memoryCache = CacheHandler.mergedHandler as Handler; | |
| const debug = process.env.NEXT_PRIVATE_DEBUG_CACHE | |
| ? console.debug.bind(console, "CustomCacheHandler:") | |
| : undefined; | |
| function getLifespanParameters( | |
| revalidate: number, | |
| timestamp: number, | |
| expire: number, | |
| ): LifespanParameters { | |
| const lastModifiedAt = Math.floor(timestamp / 1000); | |
| const staleAge = revalidate; | |
| const staleAt = lastModifiedAt + staleAge; | |
| const expireAge = expire; | |
| const expireAt = lastModifiedAt + expireAge; | |
| return { | |
| expireAge, | |
| expireAt, | |
| lastModifiedAt, | |
| revalidate, | |
| staleAge, | |
| staleAt, | |
| }; | |
| } | |
| return { | |
| async get(cacheKey, softTags) { | |
| const cachedData = await memoryCache.get(cacheKey, { | |
| implicitTags: softTags, | |
| }); | |
| if (!cachedData) { | |
| debug?.("get", cacheKey, "not found"); | |
| return undefined; | |
| } | |
| const entry = cachedData.value as CacheEntry; | |
| debug?.("get", cacheKey, "found", { | |
| ...cachedData, | |
| value: { | |
| ...entry, | |
| value: "[ReadableStream]", | |
| }, | |
| }); | |
| return { | |
| ...entry, | |
| value: new ReadableStream({ | |
| start(controller) { | |
| controller.enqueue(Buffer.from(entry.value as string, "base64")); | |
| controller.close(); | |
| }, | |
| }), | |
| }; | |
| }, | |
| async set(cacheKey, pendingEntry) { | |
| debug?.("set", cacheKey, "start"); | |
| const entry = await pendingEntry; | |
| try { | |
| const reader = entry.value.getReader(); | |
| const chunks = []; | |
| try { | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| chunks.push(value); | |
| } | |
| } finally { | |
| reader.releaseLock(); | |
| } | |
| const data = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))); | |
| const lifespan = getLifespanParameters( | |
| entry.revalidate, | |
| entry.timestamp, | |
| entry.expire, | |
| ); | |
| // If expireAt is in the past, do not cache | |
| if (lifespan !== null && Date.now() > lifespan.expireAt * 1000) { | |
| return; | |
| } | |
| const cacheHandlerValueTags = entry.tags; | |
| const cacheHandlerValue: CacheHandlerValue = { | |
| lastModified: entry.timestamp, | |
| lifespan, | |
| tags: Object.freeze(cacheHandlerValueTags), | |
| value: { | |
| ...entry, | |
| value: data.toString("base64"), | |
| }, | |
| }; | |
| await memoryCache.set(cacheKey, cacheHandlerValue); | |
| debug?.("set", cacheKey, cacheHandlerValue, "done"); | |
| } catch (err) { | |
| debug?.("set", cacheKey, "failed", err); | |
| } | |
| }, | |
| async refreshTags() { | |
| // Nothing to do for an in-memory cache handler. | |
| }, | |
| async getExpiration() { | |
| return Infinity; | |
| }, | |
| async updateTags(tags, durations) { | |
| await Promise.all( | |
| tags.map(async (tag) => memoryCache.revalidateTag(tag)), | |
| ); | |
| }, | |
| }; | |
| } | |
| export default createDefaultCacheHandler(50 * 1024 * 1024); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment