Skip to content

Commit c7f5a9d

Browse files
authored
Fixed PPR handling and loading prefetch.rsc on instrumentation (#72)
* Improves caching and pre-rendering * Improves initial cache registration by handling missing page data and prefetch data gracefully.
1 parent 37aa435 commit c7f5a9d

File tree

9 files changed

+123
-71
lines changed

9 files changed

+123
-71
lines changed
Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
interface Post {
2-
id: string
3-
title: string
4-
content: string
5-
}
6-
7-
// Next.js will invalidate the cache when a
8-
// request comes in, at most once every 60 seconds.
9-
export const revalidate = 60
10-
11-
export async function generateStaticParams() {
12-
const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) =>
13-
res.json()
14-
)
15-
return posts.map((post) => ({
16-
id: String(post.id),
17-
}))
18-
}
19-
20-
export default async function Page({
21-
params,
22-
}: {
23-
params: Promise<{ id: string }>
24-
}) {
25-
const { id } = await params
26-
const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then(
27-
(res) => res.json()
28-
)
29-
return (
30-
<main>
31-
<h1>{post.title}</h1>
32-
<p>{post.content}</p>
33-
</main>
34-
)
35-
}
2+
id: string;
3+
title: string;
4+
content: string;
5+
}
6+
7+
export const revalidate = 3600;
8+
9+
export async function generateStaticParams() {
10+
const posts: Post[] = await fetch("https://api.vercel.app/blog").then((res) =>
11+
res.json()
12+
);
13+
return posts.map((post) => ({
14+
id: String(post.id),
15+
}));
16+
}
17+
18+
export default async function Page({
19+
params,
20+
}: {
21+
params: Promise<{ id: string }>;
22+
}) {
23+
const { id } = await params;
24+
const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then(
25+
(res) => res.json()
26+
);
27+
return (
28+
<main>
29+
<h1>{post.title}</h1>
30+
<p>{post.content}</p>
31+
</main>
32+
);
33+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { MetadataRoute } from "next";
2+
3+
export default function manifest(): MetadataRoute.Manifest {
4+
return {
5+
name: "Next.js App",
6+
short_name: "Next.js App",
7+
description: "Next.js App",
8+
start_url: "/",
9+
display: "standalone",
10+
background_color: "#fff",
11+
theme_color: "#fff",
12+
icons: [
13+
{
14+
src: "/favicon.ico",
15+
sizes: "any",
16+
type: "image/x-icon",
17+
},
18+
],
19+
};
20+
}

examples/redis-minimal/src/app/ppr-example/page.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export default function Page({
99
return (
1010
<section>
1111
<h1>This will be prerendered</h1>
12+
<hr />
13+
<h1>This will be dynamic</h1>
1214
<Suspense fallback={<Skeleton />}>
1315
<Example searchParams={searchParams} />
1416
</Suspense>
@@ -17,3 +19,5 @@ export default function Page({
1719
}
1820

1921
export const experimental_ppr = true;
22+
23+
export const revalidate = 3600;

examples/redis-minimal/src/pages/posts/[id].tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const getStaticProps: GetStaticProps<PostPageProps> = async ({
3636
props: {
3737
post,
3838
},
39-
revalidate: 60,
39+
revalidate: 3600,
4040
};
4141
};
4242

packages/nextjs-cache-handler/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/nextjs-cache-handler/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"next",
1919
"redis"
2020
],
21-
"version": "2.1.0-canary.11",
21+
"version": "2.1.0-canary.12",
2222
"type": "module",
2323
"license": "MIT",
2424
"description": "Next.js cache handlers",

packages/nextjs-cache-handler/src/handlers/cache-handler.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -660,13 +660,6 @@ export class CacheHandler implements NextCacheHandler {
660660
implicitTags: softTags ?? [],
661661
});
662662

663-
if (cachedData?.value?.kind === "APP_ROUTE") {
664-
cachedData.value.body = Buffer.from(
665-
cachedData.value.body.toString(),
666-
"base64",
667-
);
668-
}
669-
670663
if (!cachedData && CacheHandler.#fallbackFalseRoutes.has(cacheKey)) {
671664
cachedData = await CacheHandler.#readPagesRouterPage(cacheKey);
672665

@@ -734,18 +727,6 @@ export class CacheHandler implements NextCacheHandler {
734727
cacheHandlerValueTags = getTagsFromHeaders(value.headers ?? {});
735728
break;
736729
}
737-
case "APP_ROUTE": {
738-
// create a new object to avoid mutating the original value
739-
value = {
740-
// replace the body with a base64 encoded string to save space
741-
body: value.body.toString("base64") as unknown as Buffer,
742-
headers: value.headers,
743-
kind: value.kind,
744-
status: value.status,
745-
};
746-
747-
break;
748-
}
749730
default: {
750731
break;
751732
}

packages/nextjs-cache-handler/src/helpers/buffer.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ export function parseBuffersToStrings(cacheHandlerValue: CacheHandlerValue) {
1414
return;
1515
}
1616

17-
const value: IncrementalCacheValue | null = {
18-
...cacheHandlerValue.value,
19-
};
17+
const value: IncrementalCacheValue | null = cacheHandlerValue.value;
2018

2119
const kind = value?.kind;
2220

@@ -27,7 +25,7 @@ export function parseBuffersToStrings(cacheHandlerValue: CacheHandlerValue) {
2725
if (appRouteValue?.body) {
2826
// Convert body Buffer to string
2927
// See: https://github.com/vercel/next.js/blob/f5444a16ec2ef7b82d30048890b613aa3865c1f1/packages/next/src/server/response-cache/types.ts#L97
30-
appRouteData.body = appRouteValue.body.toString();
28+
appRouteData.body = appRouteValue.body.toString("base64");
3129
}
3230
} else if (kind === "APP_PAGE") {
3331
const appPageData = value as unknown as RedisCompliantCachedAppPageValue;
@@ -36,7 +34,7 @@ export function parseBuffersToStrings(cacheHandlerValue: CacheHandlerValue) {
3634
if (appPageValue?.rscData) {
3735
// Convert rscData Buffer to string
3836
// See: https://github.com/vercel/next.js/blob/f5444a16ec2ef7b82d30048890b613aa3865c1f1/packages/next/src/server/response-cache/types.ts#L76
39-
appPageData.rscData = appPageValue.rscData.toString();
37+
appPageData.rscData = appPageValue.rscData.toString("base64");
4038
}
4139

4240
if (appPageValue?.segmentData) {
@@ -45,7 +43,7 @@ export function parseBuffersToStrings(cacheHandlerValue: CacheHandlerValue) {
4543
appPageData.segmentData = Object.fromEntries(
4644
Array.from(appPageValue.segmentData.entries()).map(([key, value]) => [
4745
key,
48-
value.toString(),
46+
value.toString("base64"),
4947
]),
5048
);
5149
}
@@ -63,7 +61,7 @@ export function convertStringsToBuffers(cacheValue: CacheHandlerValue) {
6361
// Convert body string to Buffer
6462
// See: https://github.com/vercel/next.js/blob/f5444a16ec2ef7b82d30048890b613aa3865c1f1/packages/next/src/server/response-cache/types.ts#L97
6563
const appRouteValue = value as unknown as CachedRouteValue;
66-
appRouteValue.body = Buffer.from(appRouteData.body, "utf-8");
64+
appRouteValue.body = Buffer.from(appRouteData.body, "base64");
6765
}
6866
} else if (kind === "APP_PAGE") {
6967
const appPageData = value as unknown as RedisCompliantCachedAppPageValue;
@@ -72,7 +70,7 @@ export function convertStringsToBuffers(cacheValue: CacheHandlerValue) {
7270
if (appPageData.rscData) {
7371
// Convert rscData string to Buffer
7472
// See: https://github.com/vercel/next.js/blob/f5444a16ec2ef7b82d30048890b613aa3865c1f1/packages/next/src/server/response-cache/types.ts#L76
75-
appPageValue.rscData = Buffer.from(appPageData.rscData, "utf-8");
73+
appPageValue.rscData = Buffer.from(appPageData.rscData, "base64");
7674
}
7775

7876
if (appPageData.segmentData) {
@@ -81,7 +79,7 @@ export function convertStringsToBuffers(cacheValue: CacheHandlerValue) {
8179
appPageValue.segmentData = new Map(
8280
Object.entries(appPageData.segmentData).map(([key, value]) => [
8381
key,
84-
Buffer.from(value, "utf-8"),
82+
Buffer.from(value, "base64"),
8583
]),
8684
);
8785
}

packages/nextjs-cache-handler/src/instrumentation/register-initial-cache.ts

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -260,16 +260,50 @@ export async function registerInitialCache(
260260
let html: string | undefined;
261261
let pageData: string | object | undefined;
262262
let meta: NextRouteMetadata | undefined;
263+
let rscData: string | undefined;
264+
265+
if (debug) {
266+
console.info(
267+
"[CacheHandler] [%s] %s",
268+
"registerInitialCache",
269+
"Reading file system cache",
270+
);
271+
}
263272

264273
try {
265-
[html, pageData, meta] = await Promise.all([
274+
[html, pageData, rscData, meta] = await Promise.all([
266275
fsPromises.readFile(`${pathToRouteFiles}.html`, "utf-8"),
267276
fsPromises
268277
.readFile(
269278
`${pathToRouteFiles}.${isAppRouter ? "rsc" : "json"}`,
270279
"utf-8",
271280
)
272-
.then((data) => (isAppRouter ? data : (JSON.parse(data) as object))),
281+
.then((data) => (isAppRouter ? data : (JSON.parse(data) as object)))
282+
.catch((error) => {
283+
console.warn(
284+
"[CacheHandler] [%s] %s %s",
285+
"registerInitialCache",
286+
"Failed to read page data, assuming it does not exist",
287+
`Error: ${error}`,
288+
);
289+
290+
return undefined;
291+
}),
292+
isAppRouter
293+
? fsPromises
294+
.readFile(`${pathToRouteFiles}.prefetch.rsc`, "utf-8")
295+
.then((data) => data)
296+
.catch((error) => {
297+
console.warn(
298+
"[CacheHandler] [%s] %s %s",
299+
"registerInitialCache",
300+
"Failed to read page prefetch data, assuming it does not exist",
301+
`Error: ${error}`,
302+
);
303+
304+
return undefined;
305+
})
306+
: undefined,
273307
isAppRouter
274308
? fsPromises
275309
.readFile(`${pathToRouteFiles}.meta`, "utf-8")
@@ -289,23 +323,40 @@ export async function registerInitialCache(
289323
return;
290324
}
291325

326+
if (debug) {
327+
console.info(
328+
"[CacheHandler] [%s] %s",
329+
"registerInitialCache",
330+
"Saving file system cache to cache handler",
331+
);
332+
}
333+
292334
try {
293335
const value: IncrementalCachedAppPageValue &
294-
Pick<IncrementalCachedPageValue, "pageData"> = {
336+
Partial<Pick<IncrementalCachedPageValue, "pageData">> = {
295337
kind: (isAppRouter ? "APP_PAGE" : "PAGES") as unknown as any,
296338
html,
297339
pageData,
298340
postponed: meta?.postponed,
299341
headers: meta?.headers,
300342
status: meta?.status,
301-
rscData: undefined,
302-
segmentData: undefined,
343+
rscData:
344+
isAppRouter && rscData ? Buffer.from(rscData, "utf-8") : undefined,
345+
segmentData: undefined, // TODO: Add segment data
303346
};
304347

305348
await cacheHandler.set(cachePath, value, {
306349
revalidate,
307350
internal_lastModified: lastModified,
308351
});
352+
353+
if (debug) {
354+
console.info(
355+
"[CacheHandler] [%s] %s",
356+
"registerInitialCache",
357+
"Saved file system cache to cache handler",
358+
);
359+
}
309360
} catch (error) {
310361
if (debug) {
311362
console.warn(

0 commit comments

Comments
 (0)