@trieb.work/nextjs-turbo-redis-cache 1.10.0-beta.14 → 1.11.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +28 -12
- package/CHANGELOG.md +6 -94
- package/README.md +94 -0
- package/dist/index.d.mts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +333 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +330 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/playwright.config.ts +8 -1
- package/src/CacheComponentsHandler.ts +471 -0
- package/src/RedisStringsHandler.ts +11 -11
- package/src/index.test.ts +1 -1
- package/src/index.ts +5 -0
- package/test/cache-components/cache-components.integration.spec.ts +188 -0
- package/test/integration/next-app-15-4-7/next.config.js +3 -0
- package/test/integration/next-app-15-4-7/pnpm-lock.yaml +1 -1
- package/test/integration/next-app-16-0-3/next.config.ts +3 -0
- package/test/integration/next-app-16-1-1-cache-components/README.md +36 -0
- package/test/integration/next-app-16-1-1-cache-components/cache-handler.js +3 -0
- package/test/integration/next-app-16-1-1-cache-components/eslint.config.mjs +18 -0
- package/test/integration/next-app-16-1-1-cache-components/next.config.ts +13 -0
- package/test/integration/next-app-16-1-1-cache-components/package.json +28 -0
- package/test/integration/next-app-16-1-1-cache-components/pnpm-lock.yaml +4128 -0
- package/test/integration/next-app-16-1-1-cache-components/postcss.config.mjs +7 -0
- package/test/integration/next-app-16-1-1-cache-components/public/file.svg +1 -0
- package/test/integration/next-app-16-1-1-cache-components/public/globe.svg +1 -0
- package/test/integration/next-app-16-1-1-cache-components/public/next.svg +1 -0
- package/test/integration/next-app-16-1-1-cache-components/public/public/file.svg +1 -0
- package/test/integration/next-app-16-1-1-cache-components/public/public/globe.svg +1 -0
- package/test/integration/next-app-16-1-1-cache-components/public/public/next.svg +1 -0
- package/test/integration/next-app-16-1-1-cache-components/public/public/vercel.svg +1 -0
- package/test/integration/next-app-16-1-1-cache-components/public/public/window.svg +1 -0
- package/test/integration/next-app-16-1-1-cache-components/public/vercel.svg +1 -0
- package/test/integration/next-app-16-1-1-cache-components/public/window.svg +1 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/api/cached-static-fetch/route.ts +19 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/api/cached-with-tag/route.ts +21 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/api/revalidate-tag/route.ts +19 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/api/revalidated-fetch/route.ts +19 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/cache-lab/cachelife-short/page.tsx +110 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/cache-lab/page.tsx +90 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +127 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +130 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +127 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +110 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/favicon.ico +0 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/globals.css +26 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/layout.tsx +57 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/page.tsx +755 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/revalidation-interface.tsx +267 -0
- package/test/integration/next-app-16-1-1-cache-components/src/app/update-tag-test/page.tsx +22 -0
- package/test/integration/next-app-16-1-1-cache-components/tsconfig.json +34 -0
- package/tests/cache-lab.spec.ts +157 -0
- package/vitest.cache-components.config.ts +16 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
package/test/integration/next-app-16-1-1-cache-components/src/app/api/cached-static-fetch/route.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
let counter = 0;
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const data = await getSimpleCachedData();
|
|
7
|
+
return NextResponse.json(data);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function getSimpleCachedData() {
|
|
11
|
+
'use cache';
|
|
12
|
+
|
|
13
|
+
counter++;
|
|
14
|
+
return {
|
|
15
|
+
counter,
|
|
16
|
+
timestamp: Date.now(),
|
|
17
|
+
message: 'Simple cached data without tags',
|
|
18
|
+
};
|
|
19
|
+
}
|
package/test/integration/next-app-16-1-1-cache-components/src/app/api/cached-with-tag/route.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { cacheTag } from 'next/cache';
|
|
3
|
+
|
|
4
|
+
let counter = 0;
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
const data = await getCachedData();
|
|
8
|
+
return NextResponse.json(data);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function getCachedData() {
|
|
12
|
+
'use cache';
|
|
13
|
+
cacheTag('test-tag');
|
|
14
|
+
|
|
15
|
+
counter++;
|
|
16
|
+
return {
|
|
17
|
+
counter,
|
|
18
|
+
timestamp: Date.now(),
|
|
19
|
+
message: 'This data should be cached',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { revalidateTag } from 'next/cache';
|
|
3
|
+
|
|
4
|
+
export async function POST(request: Request) {
|
|
5
|
+
const body = await request.json();
|
|
6
|
+
const { tag } = body;
|
|
7
|
+
|
|
8
|
+
if (!tag) {
|
|
9
|
+
return NextResponse.json({ error: 'Tag is required' }, { status: 400 });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
revalidateTag(tag, { expire: 1 });
|
|
13
|
+
|
|
14
|
+
return NextResponse.json({
|
|
15
|
+
revalidated: true,
|
|
16
|
+
tag,
|
|
17
|
+
timestamp: Date.now(),
|
|
18
|
+
});
|
|
19
|
+
}
|
package/test/integration/next-app-16-1-1-cache-components/src/app/api/revalidated-fetch/route.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
let counter = 0;
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const data = await getCachedData();
|
|
7
|
+
return NextResponse.json(data);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function getCachedData() {
|
|
11
|
+
'use cache';
|
|
12
|
+
|
|
13
|
+
counter++;
|
|
14
|
+
return {
|
|
15
|
+
counter,
|
|
16
|
+
timestamp: Date.now(),
|
|
17
|
+
message: 'Revalidated cached data',
|
|
18
|
+
};
|
|
19
|
+
}
|
package/test/integration/next-app-16-1-1-cache-components/src/app/cache-lab/cachelife-short/page.tsx
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { cacheLife, cacheTag, revalidateTag } from 'next/cache';
|
|
3
|
+
import { Suspense } from 'react';
|
|
4
|
+
|
|
5
|
+
async function getCacheLifeSample() {
|
|
6
|
+
'use cache';
|
|
7
|
+
|
|
8
|
+
// Very short timings for manual testing.
|
|
9
|
+
cacheLife({
|
|
10
|
+
stale: 2,
|
|
11
|
+
revalidate: 4,
|
|
12
|
+
expire: 10,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
cacheTag('cache-lab:cachelife-short');
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
createdAt: Date.now(),
|
|
19
|
+
random: Math.random(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function triggerRevalidateTag() {
|
|
24
|
+
'use server';
|
|
25
|
+
revalidateTag('cache-lab:cachelife-short', { expire: 1 });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function CachedDataPanel() {
|
|
29
|
+
const data = await getCacheLifeSample();
|
|
30
|
+
return (
|
|
31
|
+
<div className="rounded-lg border p-5">
|
|
32
|
+
<dl className="grid grid-cols-1 gap-3 text-sm">
|
|
33
|
+
<div>
|
|
34
|
+
<dt className="font-medium">createdAt</dt>
|
|
35
|
+
<dd data-testid="createdAt" className="font-mono text-slate-700">
|
|
36
|
+
{data.createdAt}
|
|
37
|
+
</dd>
|
|
38
|
+
</div>
|
|
39
|
+
<div>
|
|
40
|
+
<dt className="font-medium">random</dt>
|
|
41
|
+
<dd data-testid="random" className="font-mono text-slate-700">
|
|
42
|
+
{data.random}
|
|
43
|
+
</dd>
|
|
44
|
+
</div>
|
|
45
|
+
<div>
|
|
46
|
+
<dt className="font-medium">cacheLife config</dt>
|
|
47
|
+
<dd className="font-mono text-slate-700">
|
|
48
|
+
{'{ stale: 2, revalidate: 4, expire: 10 }'}
|
|
49
|
+
</dd>
|
|
50
|
+
</div>
|
|
51
|
+
</dl>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default function CacheLifeShortPage() {
|
|
57
|
+
return (
|
|
58
|
+
<main className="mx-auto max-w-3xl p-10">
|
|
59
|
+
<div className="mb-6">
|
|
60
|
+
<Link
|
|
61
|
+
className="text-sm text-blue-600 hover:underline"
|
|
62
|
+
href="/cache-lab"
|
|
63
|
+
>
|
|
64
|
+
← Back to Cache Lab
|
|
65
|
+
</Link>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<header className="mb-6">
|
|
69
|
+
<h1 className="text-2xl font-semibold">cacheLife: short timings</h1>
|
|
70
|
+
<p className="mt-2 text-sm text-slate-600">
|
|
71
|
+
This uses <code>cacheLife</code> with short durations so you can
|
|
72
|
+
observe stale / revalidate / expire behavior quickly.
|
|
73
|
+
</p>
|
|
74
|
+
</header>
|
|
75
|
+
|
|
76
|
+
<Suspense
|
|
77
|
+
fallback={
|
|
78
|
+
<div className="rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
79
|
+
Loading cached content…
|
|
80
|
+
</div>
|
|
81
|
+
}
|
|
82
|
+
>
|
|
83
|
+
<CachedDataPanel />
|
|
84
|
+
</Suspense>
|
|
85
|
+
|
|
86
|
+
<form action={triggerRevalidateTag} className="mt-6">
|
|
87
|
+
<button
|
|
88
|
+
className="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
|
|
89
|
+
type="submit"
|
|
90
|
+
>
|
|
91
|
+
revalidateTag('cache-lab:cachelife-short')
|
|
92
|
+
</button>
|
|
93
|
+
</form>
|
|
94
|
+
|
|
95
|
+
<div className="mt-6 rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
96
|
+
<div className="space-y-2">
|
|
97
|
+
<p>
|
|
98
|
+
Expected: reload a few times. Within a couple seconds, the cached
|
|
99
|
+
value may become stale; after revalidate, a fresh value should
|
|
100
|
+
appear.
|
|
101
|
+
</p>
|
|
102
|
+
<p>
|
|
103
|
+
This page deliberately puts the async work behind{' '}
|
|
104
|
+
<code>{'<Suspense>'}</code> to avoid the "Blocking Route" warning.
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</main>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
|
|
3
|
+
export default function CacheLabIndexPage() {
|
|
4
|
+
return (
|
|
5
|
+
<main className="mx-auto max-w-5xl p-10">
|
|
6
|
+
<header className="mb-10">
|
|
7
|
+
<h1 className="text-3xl font-semibold">Cache Lab</h1>
|
|
8
|
+
<p className="mt-2 text-slate-600">
|
|
9
|
+
Manual test pages for Next.js Cache Components (use cache, cacheLife,
|
|
10
|
+
tags, updateTag/revalidateTag, and Suspense boundaries).
|
|
11
|
+
</p>
|
|
12
|
+
</header>
|
|
13
|
+
|
|
14
|
+
<section className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
15
|
+
<Link
|
|
16
|
+
className="rounded-lg border p-5 hover:bg-slate-50"
|
|
17
|
+
href="/cache-lab/use-cache-nondeterministic"
|
|
18
|
+
>
|
|
19
|
+
<h2 className="text-lg font-medium">use cache: non-deterministic</h2>
|
|
20
|
+
<p className="mt-1 text-sm text-slate-600">
|
|
21
|
+
Random/Date/UUID should stay identical across reloads until you
|
|
22
|
+
invalidate.
|
|
23
|
+
</p>
|
|
24
|
+
</Link>
|
|
25
|
+
|
|
26
|
+
<Link
|
|
27
|
+
className="rounded-lg border p-5 hover:bg-slate-50"
|
|
28
|
+
href="/cache-lab/cachelife-short"
|
|
29
|
+
>
|
|
30
|
+
<h2 className="text-lg font-medium">cacheLife: short timings</h2>
|
|
31
|
+
<p className="mt-1 text-sm text-slate-600">
|
|
32
|
+
Uses very small stale/revalidate/expire values so you can observe
|
|
33
|
+
SWR + expiry quickly.
|
|
34
|
+
</p>
|
|
35
|
+
</Link>
|
|
36
|
+
|
|
37
|
+
<Link
|
|
38
|
+
className="rounded-lg border p-5 hover:bg-slate-50"
|
|
39
|
+
href="/cache-lab/tag-invalidation"
|
|
40
|
+
>
|
|
41
|
+
<h2 className="text-lg font-medium">
|
|
42
|
+
Tags: updateTag vs revalidateTag
|
|
43
|
+
</h2>
|
|
44
|
+
<p className="mt-1 text-sm text-slate-600">
|
|
45
|
+
Cached component tagged via cacheTag. Trigger updateTag (immediate)
|
|
46
|
+
or revalidateTag (SWR).
|
|
47
|
+
</p>
|
|
48
|
+
</Link>
|
|
49
|
+
|
|
50
|
+
<Link
|
|
51
|
+
className="rounded-lg border p-5 hover:bg-slate-50"
|
|
52
|
+
href="/cache-lab/stale-while-revalidate"
|
|
53
|
+
>
|
|
54
|
+
<h2 className="text-lg font-medium">
|
|
55
|
+
SWR: stale-while-revalidate (slow)
|
|
56
|
+
</h2>
|
|
57
|
+
<p className="mt-1 text-sm text-slate-600">
|
|
58
|
+
Forces slow recomputation so you can see whether stale is served
|
|
59
|
+
immediately while refresh happens in the background.
|
|
60
|
+
</p>
|
|
61
|
+
</Link>
|
|
62
|
+
|
|
63
|
+
<Link
|
|
64
|
+
className="rounded-lg border p-5 hover:bg-slate-50"
|
|
65
|
+
href="/cache-lab/runtime-data-suspense"
|
|
66
|
+
>
|
|
67
|
+
<h2 className="text-lg font-medium">Runtime data + Suspense</h2>
|
|
68
|
+
<p className="mt-1 text-sm text-slate-600">
|
|
69
|
+
Reads cookies() at request time in a non-cached component and passes
|
|
70
|
+
it into a cached component to show correct boundaries.
|
|
71
|
+
</p>
|
|
72
|
+
</Link>
|
|
73
|
+
</section>
|
|
74
|
+
|
|
75
|
+
<section className="mt-12 rounded-lg border bg-slate-50 p-5">
|
|
76
|
+
<h3 className="font-medium">How to use</h3>
|
|
77
|
+
<div className="mt-2 space-y-2 text-sm text-slate-700">
|
|
78
|
+
<p>
|
|
79
|
+
Open each page in 2 tabs and compare behavior while reloading.
|
|
80
|
+
Observe which values stay stable, and which update.
|
|
81
|
+
</p>
|
|
82
|
+
<p>
|
|
83
|
+
Use the buttons on each page to trigger tag invalidation and observe
|
|
84
|
+
how quickly new content becomes visible.
|
|
85
|
+
</p>
|
|
86
|
+
</div>
|
|
87
|
+
</section>
|
|
88
|
+
</main>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { Suspense } from 'react';
|
|
3
|
+
import { cookies } from 'next/headers';
|
|
4
|
+
|
|
5
|
+
export default function RuntimeDataSuspensePage() {
|
|
6
|
+
return (
|
|
7
|
+
<main className="mx-auto max-w-3xl p-10">
|
|
8
|
+
<div className="mb-6">
|
|
9
|
+
<Link
|
|
10
|
+
className="text-sm text-blue-600 hover:underline"
|
|
11
|
+
href="/cache-lab"
|
|
12
|
+
>
|
|
13
|
+
← Back to Cache Lab
|
|
14
|
+
</Link>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<header className="mb-6">
|
|
18
|
+
<h1 className="text-2xl font-semibold">Runtime data + Suspense</h1>
|
|
19
|
+
<p className="mt-2 text-sm text-slate-600">
|
|
20
|
+
This page reads <code>cookies()</code> at request time (runtime data),
|
|
21
|
+
then passes a value into a cached function. The runtime read is
|
|
22
|
+
wrapped in <code>{'<Suspense>'}</code> so it is an explicit dynamic
|
|
23
|
+
boundary.
|
|
24
|
+
</p>
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
<Suspense
|
|
28
|
+
fallback={
|
|
29
|
+
<div className="rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
30
|
+
Loading runtime cookie…
|
|
31
|
+
</div>
|
|
32
|
+
}
|
|
33
|
+
>
|
|
34
|
+
<RuntimeCookieBlock />
|
|
35
|
+
</Suspense>
|
|
36
|
+
|
|
37
|
+
<div className="mt-6 rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
38
|
+
<div className="space-y-2">
|
|
39
|
+
<p>
|
|
40
|
+
Expected: changing the cookie changes the <em>input</em> to the
|
|
41
|
+
cached function, producing a different cache entry.
|
|
42
|
+
</p>
|
|
43
|
+
<p>
|
|
44
|
+
The cached function itself uses <code>use cache</code>, so for the
|
|
45
|
+
same cookie value, it should return a stable payload across reloads.
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</main>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function setCookie(value: string) {
|
|
54
|
+
'use server';
|
|
55
|
+
const store = await cookies();
|
|
56
|
+
store.set('cache_lab_session', value, { path: '/' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function setCookieUserA() {
|
|
60
|
+
'use server';
|
|
61
|
+
await setCookie('user-a');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function setCookieUserB() {
|
|
65
|
+
'use server';
|
|
66
|
+
await setCookie('user-b');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function RuntimeCookieBlock() {
|
|
70
|
+
const store = await cookies();
|
|
71
|
+
const sessionId = store.get('cache_lab_session')?.value || 'anonymous';
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className="rounded-lg border p-5">
|
|
75
|
+
<div className="mb-4 text-sm">
|
|
76
|
+
<div>
|
|
77
|
+
<span className="font-medium">cookie cache_lab_session:</span>{' '}
|
|
78
|
+
<span data-testid="cookie" className="font-mono text-slate-700">
|
|
79
|
+
{sessionId}
|
|
80
|
+
</span>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div className="flex flex-col gap-3 sm:flex-row">
|
|
85
|
+
<form action={setCookieUserA}>
|
|
86
|
+
<button
|
|
87
|
+
className="w-full rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
|
|
88
|
+
type="submit"
|
|
89
|
+
>
|
|
90
|
+
Set cookie: user-a
|
|
91
|
+
</button>
|
|
92
|
+
</form>
|
|
93
|
+
<form action={setCookieUserB}>
|
|
94
|
+
<button
|
|
95
|
+
className="w-full rounded-md border border-blue-600 bg-white px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50"
|
|
96
|
+
type="submit"
|
|
97
|
+
>
|
|
98
|
+
Set cookie: user-b
|
|
99
|
+
</button>
|
|
100
|
+
</form>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div className="mt-5">
|
|
104
|
+
<CachedPerSession sessionId={sessionId} />
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function CachedPerSession({ sessionId }: { sessionId: string }) {
|
|
111
|
+
'use cache';
|
|
112
|
+
|
|
113
|
+
const payload = {
|
|
114
|
+
sessionId,
|
|
115
|
+
createdAt: Date.now(),
|
|
116
|
+
random: Math.random(),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div className="rounded-md bg-slate-50 p-4 text-sm">
|
|
121
|
+
<div className="font-medium">Cached payload (keyed by sessionId)</div>
|
|
122
|
+
<pre data-testid="payload" className="mt-2 overflow-x-auto text-xs">
|
|
123
|
+
{JSON.stringify(payload, null, 2)}
|
|
124
|
+
</pre>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { Suspense } from 'react';
|
|
3
|
+
import { cacheLife, cacheTag, revalidateTag } from 'next/cache';
|
|
4
|
+
|
|
5
|
+
function sleep(ms: number): Promise<void> {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function getSlowTaggedValue() {
|
|
10
|
+
'use cache';
|
|
11
|
+
|
|
12
|
+
// Keep this short so you can observe SWR behavior quickly.
|
|
13
|
+
// stale: after 2s the entry is considered stale
|
|
14
|
+
// revalidate: after 8s Next may attempt to refresh
|
|
15
|
+
// expire: after 60s it must be recomputed
|
|
16
|
+
cacheLife({ stale: 2, revalidate: 8, expire: 60 });
|
|
17
|
+
cacheTag('cache-lab:swr');
|
|
18
|
+
|
|
19
|
+
// Make regeneration visibly expensive so you can tell whether the request was
|
|
20
|
+
// served from stale cache (fast) or blocked on recomputation (slow).
|
|
21
|
+
await sleep(3000);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
computedAt: Date.now(),
|
|
25
|
+
value: Math.random(),
|
|
26
|
+
note: 'This function always sleeps ~3s when it actually executes.',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function triggerRevalidateTag() {
|
|
31
|
+
'use server';
|
|
32
|
+
revalidateTag('cache-lab:swr', { expire: 1 });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function CachedPanel() {
|
|
36
|
+
const data = await getSlowTaggedValue();
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="rounded-lg border p-5">
|
|
40
|
+
<div className="text-sm text-slate-700">
|
|
41
|
+
<div>
|
|
42
|
+
<span className="font-medium">computedAt:</span>{' '}
|
|
43
|
+
<span data-testid="computedAt" className="font-mono">
|
|
44
|
+
{data.computedAt}
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="mt-1">
|
|
48
|
+
<span className="font-medium">value:</span>{' '}
|
|
49
|
+
<span data-testid="value" className="font-mono">
|
|
50
|
+
{data.value}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div className="mt-1 text-xs text-slate-500">{data.note}</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default function StaleWhileRevalidatePage() {
|
|
60
|
+
return (
|
|
61
|
+
<main className="mx-auto max-w-3xl p-10">
|
|
62
|
+
<div className="mb-6">
|
|
63
|
+
<Link
|
|
64
|
+
className="text-sm text-blue-600 hover:underline"
|
|
65
|
+
href="/cache-lab"
|
|
66
|
+
>
|
|
67
|
+
← Back to Cache Lab
|
|
68
|
+
</Link>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<header className="mb-6">
|
|
72
|
+
<h1 className="text-2xl font-semibold">
|
|
73
|
+
SWR: stale-while-revalidate (slow)
|
|
74
|
+
</h1>
|
|
75
|
+
<p className="mt-2 text-sm text-slate-600">
|
|
76
|
+
This page is designed to make stale-while-revalidate behavior visible.
|
|
77
|
+
The cached function sleeps ~3 seconds whenever it really runs.
|
|
78
|
+
</p>
|
|
79
|
+
</header>
|
|
80
|
+
|
|
81
|
+
<Suspense
|
|
82
|
+
fallback={
|
|
83
|
+
<div className="rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
84
|
+
Loading cached content… (first time can take ~3s)
|
|
85
|
+
</div>
|
|
86
|
+
}
|
|
87
|
+
>
|
|
88
|
+
<CachedPanel />
|
|
89
|
+
</Suspense>
|
|
90
|
+
|
|
91
|
+
<div className="mt-6 flex flex-col gap-3 sm:flex-row">
|
|
92
|
+
<form action={triggerRevalidateTag}>
|
|
93
|
+
<button
|
|
94
|
+
className="w-full rounded-md border border-blue-600 bg-white px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50"
|
|
95
|
+
type="submit"
|
|
96
|
+
>
|
|
97
|
+
revalidateTag('cache-lab:swr')
|
|
98
|
+
</button>
|
|
99
|
+
</form>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className="mt-6 rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
103
|
+
<div className="space-y-2">
|
|
104
|
+
<p className="font-medium">How to observe SWR (non-blocking)</p>
|
|
105
|
+
<p>1) Load this page once (expect ~3s on the very first load).</p>
|
|
106
|
+
<p>
|
|
107
|
+
2) Wait ~3 seconds so the entry becomes <code>stale</code>{' '}
|
|
108
|
+
(stale=2s).
|
|
109
|
+
</p>
|
|
110
|
+
<p>
|
|
111
|
+
3) Click <code>revalidateTag</code>.
|
|
112
|
+
</p>
|
|
113
|
+
<p>4) Immediately reload the page:</p>
|
|
114
|
+
<div className="rounded bg-white p-3 text-xs">
|
|
115
|
+
If SWR is working, the reload should be fast and you may still see
|
|
116
|
+
the old computedAt/value (stale served).
|
|
117
|
+
<br />
|
|
118
|
+
Reload again after ~3-4 seconds and you should see a new
|
|
119
|
+
computedAt/value.
|
|
120
|
+
<br />
|
|
121
|
+
If SWR is not working, the reload will block for ~3 seconds.
|
|
122
|
+
</div>
|
|
123
|
+
<div className="text-xs text-slate-500">
|
|
124
|
+
cacheLife: {'{ stale: 2, revalidate: 8, expire: 60 }'}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</main>
|
|
129
|
+
);
|
|
130
|
+
}
|