Skip to content

Commit

Permalink
some support for suspense cache soft/implicit tags (#569)
Browse files Browse the repository at this point in the history
  • Loading branch information
james-elicx authored Dec 2, 2023
1 parent 1e73555 commit bc216b4
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/good-squids-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cloudflare/next-on-pages': patch
---

Improved support for newer version of Next.js with the suspense cache through trying to handle soft/implicit tags properly
5 changes: 5 additions & 0 deletions .changeset/shaggy-wombats-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cloudflare/next-on-pages': patch
---

Fix old version of Next.js not updating a cache entry properly due to not receving the correct shape they expected.
22 changes: 13 additions & 9 deletions packages/next-on-pages/templates/_worker.js/utils/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { SUSPENSE_CACHE_URL } from '../../cache';

// https://github.com/vercel/next.js/blob/48a566bc/packages/next/src/server/lib/incremental-cache/fetch-cache.ts#L19
const CACHE_TAGS_HEADER = 'x-vercel-cache-tags';
// https://github.com/vercel/next.js/blob/ba23d986/packages/next/src/lib/constants.ts#L18
const NEXT_CACHE_SOFT_TAGS_HEADER = 'x-next-cache-soft-tags';

/**
* Handles an internal request to the suspense cache.
Expand Down Expand Up @@ -37,8 +39,13 @@ export async function handleSuspenseCacheRequest(request: Request) {

switch (request.method) {
case 'GET': {
const softTags = getTagsFromHeader(
request,
NEXT_CACHE_SOFT_TAGS_HEADER,
);

// Retrieve the value from the cache.
const data = await cache.get(cacheKey);
const data = await cache.get(cacheKey, { softTags });
if (!data) return new Response(null, { status: 404 });

return new Response(JSON.stringify(data.value), {
Expand All @@ -55,14 +62,7 @@ export async function handleSuspenseCacheRequest(request: Request) {
const body = await request.json<IncrementalCacheValue>();
// Falling back to the cache tags header for Next.js 13.5+
if (body.data.tags === undefined) {
body.tags ??=
request.headers
.get(CACHE_TAGS_HEADER)
?.split(',')
?.filter(Boolean) ?? [];
} else {
body.tags = body.data.tags;
delete body.data.tags;
body.tags ??= getTagsFromHeader(request, CACHE_TAGS_HEADER) ?? [];
}

await cache.set(cacheKey, body);
Expand Down Expand Up @@ -104,3 +104,7 @@ async function getInternalCacheAdaptor(
const adaptor = await import(`./__next-on-pages-dist__/cache/${type}.js`);
return new adaptor.default();
}

function getTagsFromHeader(req: Request, key: string): string[] | undefined {
return req.headers.get(key)?.split(',')?.filter(Boolean);
}
34 changes: 27 additions & 7 deletions packages/next-on-pages/templates/cache/adaptor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// NOTE: This is given the same name that the environment variable has in the Next.js source code.
export const SUSPENSE_CACHE_URL = 'INTERNAL_SUSPENSE_CACHE_HOSTNAME.local';

// https://github.com/vercel/next.js/blob/f6babb4/packages/next/src/lib/constants.ts#23
const NEXT_CACHE_IMPLICIT_TAG_ID = '_N_T_';

// Set to track the revalidated tags in requests.
const revalidatedTags = new Set<string>();

Expand Down Expand Up @@ -54,10 +57,17 @@ export class CacheAdaptor {
switch (newEntry.value?.kind) {
case 'FETCH': {
// Update the tags with the cache key.
const tags = newEntry.value.tags ?? [];
const tags = getTagsFromEntry(newEntry);
await this.setTags(tags, { cacheKey: key });

getDerivedTags(tags).forEach(tag => revalidatedTags.delete(tag));
const derivedTags = getDerivedTags(tags);
const implicitTags = derivedTags.map(
tag => `${NEXT_CACHE_IMPLICIT_TAG_ID}${tag}`,
);

[...derivedTags, ...implicitTags].forEach(tag =>
revalidatedTags.delete(tag),
);
}
}
}
Expand All @@ -66,9 +76,13 @@ export class CacheAdaptor {
* Retrieves an entry from the suspense cache.
*
* @param key Key for the item in the suspense cache.
* @param opts Soft cache tags used when checking if an entry is stale.
* @returns The cached value, or null if no entry exists.
*/
public async get(key: string): Promise<CacheHandlerValue | null> {
public async get(
key: string,
{ softTags }: { softTags?: string[] },
): Promise<CacheHandlerValue | null> {
// Get entry from the cache.
const entry = await this.retrieve(key);
if (!entry) return null;
Expand All @@ -87,10 +101,12 @@ export class CacheAdaptor {
await this.loadTagsManifest();

// Check if the cache entry is stale or fresh based on the tags.
const tags = getDerivedTags(
data.value.tags ?? data.value.data.tags ?? [],
);
const isStale = tags.some(tag => {
const tags = getTagsFromEntry(data);
const combinedTags = softTags
? [...tags, ...softTags]
: getDerivedTags(tags);

const isStale = combinedTags.some(tag => {
// If a revalidation has been triggered, the current entry is stale.
if (revalidatedTags.has(tag)) return true;

Expand Down Expand Up @@ -257,3 +273,7 @@ export function getDerivedTags(tags: string[]): string[] {
}
return derivedTags;
}

export function getTagsFromEntry(entry: CacheHandlerValue): string[] {
return entry.value?.tags ?? entry.value?.data?.tags ?? [];
}

0 comments on commit bc216b4

Please sign in to comment.