@trieb.work/nextjs-turbo-redis-cache 1.15.0 → 1.16.0
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/.cursor/skills/testing/SKILL.md +121 -0
- package/.github/workflows/ci.yml +199 -69
- package/.github/workflows/release.yml +9 -1
- package/ARCHITECTURE.md +431 -0
- package/CHANGELOG.md +28 -0
- package/README.md +77 -49
- package/dist/index.d.mts +71 -45
- package/dist/index.d.ts +71 -45
- package/dist/index.js +107 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +107 -21
- package/dist/index.mjs.map +1 -1
- package/docs/index.html +7 -6
- package/eslint.config.mjs +1 -1
- package/package.json +15 -16
- package/playwright.config.ts +5 -3
- package/src/CacheComponentsHandler.ts +57 -2
- package/src/RedisStringsHandler.ts +125 -66
- package/src/SyncedMap.ts +62 -13
- package/test/README.md +194 -0
- package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/package.json +6 -6
- package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-15-4-11}/pnpm-lock.yaml +89 -105
- package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/package.json +3 -3
- package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/pnpm-lock.yaml +53 -53
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/package.json +28 -0
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/pnpm-lock.yaml +4206 -0
- package/test/nextjs-test-projects/next-app-16-2-6/eslint.config.mjs +18 -0
- package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/package.json +3 -3
- package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/pnpm-lock.yaml +54 -60
- package/test/nextjs-test-projects/next-app-16-2-6/postcss.config.mjs +7 -0
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/favicon.ico +0 -0
- package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-2-6}/tsconfig.json +9 -2
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/cache-handler.js +3 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/next.config.ts +13 -0
- package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6-cache-components}/package.json +3 -3
- package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6-cache-components}/pnpm-lock.yaml +54 -60
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-static-fetch/route.ts +19 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-cachelife/route.ts +24 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-tag/route.ts +21 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidate-tag/route.ts +19 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidated-fetch/route.ts +19 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/cachelife-short/page.tsx +110 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/page.tsx +90 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +127 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +130 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +127 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +110 -0
- package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/layout.tsx +0 -2
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/update-tag-test/page.tsx +22 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/README.md +16 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/eslint.config.mjs +18 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/next.config.ts +7 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/package.json +26 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/pnpm-lock.yaml +3896 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/api/revalidate.ts +24 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/index.tsx +11 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/isr/[slug].tsx +49 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/static-forever.tsx +20 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/tsconfig.json +34 -0
- package/{tests → test/playwright}/update-tag.spec.ts +1 -1
- package/test/{integration → vitest/integration}/build-id-prefix.integration.test.ts +2 -2
- package/test/{cache-components/cache-components.integration.spec.ts → vitest/integration/cache-components/cache-components.integration.test.ts} +7 -3
- package/test/{cache-components/redis-kill-reconnect.spec.ts → vitest/integration/cache-components/redis-kill-reconnect.test.ts} +28 -7
- package/test/vitest/integration/cache-components/redis-quit-vs-disconnect.test.ts +59 -0
- package/test/vitest/integration/cache-components/redis-subscriber-outage.test.ts +100 -0
- package/test/{cache-components → vitest/integration/cache-components}/scripts/redis-kill-reconnect.ts +31 -8
- package/test/vitest/integration/cache-components/scripts/redis-quit-vs-disconnect.ts +113 -0
- package/test/vitest/integration/cache-components/scripts/redis-subscriber-outage.ts +379 -0
- package/test/vitest/integration/cache-components/scripts/redis-test-helpers.ts +155 -0
- package/test/{integration → vitest/integration}/nextjs-cache-handler.integration.test.ts +15 -9
- package/test/vitest/integration/pages-router.integration.test.ts +420 -0
- package/test/vitest/unit/SyncedMap-reconnect-repro.test.ts +154 -0
- package/{src → test/vitest/unit}/index.test.ts +3 -3
- package/test/vitest/unit/pages-router-kinds.test.ts +292 -0
- package/test/{cache-components/reconnect-socket-already-opened.spec.ts → vitest/unit/reconnect-socket-already-opened.test.ts} +1 -1
- package/{src → test/vitest/unit}/serializer.test.ts +7 -3
- package/{src → test/vitest/unit}/utils/prefix.test.ts +1 -1
- package/vite.config.ts +4 -3
- package/vitest.cache-components.config.ts +1 -1
- package/test/browser/update-tag.browser.test.ts +0 -41
- package/test/integration/next-app-15-0-3/next.config.js +0 -6
- package/test/integration/next-app-15-0-3/package-lock.json +0 -5833
- package/test/integration/next-app-15-0-3/pnpm-lock.yaml +0 -3710
- package/test/integration/next-app-15-3-2/next.config.js +0 -6
- package/test/integration/next-app-15-3-2/package.json +0 -33
- package/test/integration/next-app-15-3-2/pnpm-lock.yaml +0 -3720
- package/test/integration/next-app-15-3-2/postcss.config.mjs +0 -5
- package/test/integration/next-app-15-3-2/src/app/api/revalidateTag/route.ts +0 -15
- package/test/integration/next-app-15-4-7/eslint.config.mjs +0 -16
- package/test/integration/next-app-15-4-7/package-lock.json +0 -5969
- package/test/integration/next-app-15-4-7/package.json +0 -33
- package/test/integration/next-app-15-4-7/postcss.config.mjs +0 -5
- package/test/integration/next-app-15-4-7/src/app/api/revalidateTag/route.ts +0 -15
- package/test/integration/next-app-15-4-7/tsconfig.json +0 -27
- package/test/integration/next-app-16-0-3/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/integration/next-app-16-0-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/integration/next-app-16-0-3/src/app/api/revalidatePath/route.ts +0 -15
- package/test/integration/next-app-16-0-3/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/integration/next-app-16-0-3/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-0-3/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-2-3/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/integration/next-app-16-2-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/integration/next-app-16-2-3/src/app/api/revalidatePath/route.ts +0 -15
- package/test/integration/next-app-16-2-3/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/integration/next-app-16-2-3/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/integration/next-app-16-2-3/src/app/globals.css +0 -26
- package/test/integration/next-app-16-2-3/src/app/layout.tsx +0 -59
- package/test/integration/next-app-16-2-3/src/app/page.tsx +0 -755
- package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-2-3/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-2-3/src/app/revalidation-interface.tsx +0 -267
- package/test/integration/next-app-16-2-3-cache-components/public/file.svg +0 -1
- package/test/integration/next-app-16-2-3-cache-components/public/globe.svg +0 -1
- package/test/integration/next-app-16-2-3-cache-components/public/next.svg +0 -1
- package/test/integration/next-app-16-2-3-cache-components/public/vercel.svg +0 -1
- package/test/integration/next-app-16-2-3-cache-components/public/window.svg +0 -1
- package/test/integration/next-app-customized/README.md +0 -36
- package/test/integration/next-app-customized/eslint.config.mjs +0 -16
- package/vitest.browser.config.ts +0 -10
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/README.md +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/eslint.config.mjs +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-15-4-11}/next.config.js +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-15-4-11}/package-lock.json +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/postcss.config.mjs +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/file.svg +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/globe.svg +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/next.svg +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/vercel.svg +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/window.svg +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/cached-static-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidatePath/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidateTag/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/uncached-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/globals.css +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/layout.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/no-fetch/default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/revalidation-interface.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/tsconfig.json +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/README.md +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/eslint.config.mjs +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/next.config.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/postcss.config.mjs +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/cached-static-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidatePath/route.ts +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidateTag/route.ts +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/uncached-fetch/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/src/app/favicon.ico +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/globals.css +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/layout.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/no-fetch/default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/revalidation-interface.tsx +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/src/app/update-tag-test/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/tsconfig.json +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/README.md +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/cache-handler.js +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/eslint.config.mjs +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/next.config.ts +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/postcss.config.mjs +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/file.svg +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/globe.svg +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/next.svg +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/vercel.svg +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/window.svg +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/cached-static-fetch/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/cached-with-cachelife/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/cached-with-tag/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/revalidate-tag/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/cachelife-short/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/tag-invalidation/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/favicon.ico +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/globals.css +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/layout.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/revalidation-interface.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/update-tag-test/page.tsx +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/tsconfig.json +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/README.md +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/next.config.ts +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/cached-static-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidatePath/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidateTag/route.ts +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/uncached-fetch/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/globals.css +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/layout.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/no-fetch/default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/revalidation-interface.tsx +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/src/app/update-tag-test/page.tsx +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/README.md +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/eslint.config.mjs +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/postcss.config.mjs +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/file.svg +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/globe.svg +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/next.svg +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/vercel.svg +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/window.svg +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/favicon.ico +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/globals.css +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/page.tsx +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/revalidation-interface.tsx +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/tsconfig.json +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-customized}/README.md +0 -0
- /package/test/{integration → nextjs-test-projects}/next-app-customized/customized-cache-handler.js +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-customized}/eslint.config.mjs +0 -0
- /package/test/{integration → nextjs-test-projects}/next-app-customized/next.config.js +0 -0
- /package/{tests → test/playwright}/cache-lab.spec.ts +0 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@trieb.work/nextjs-turbo-redis-cache)
|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
The ultimate Redis Cache Handler for Next.js 15 / 16 and the
|
|
6
|
+
The ultimate Redis Cache Handler for Next.js 15 / 16, supporting both the App Router and the Pages Router. Built for production-ready, large-scale projects, it delivers unparalleled performance and efficiency with features tailored for high-traffic applications. This package has been created after extensibly testing the @neshca package and finding several major issues with it.
|
|
7
7
|
|
|
8
8
|
Key Features:
|
|
9
9
|
|
|
@@ -15,20 +15,25 @@ Key Features:
|
|
|
15
15
|
|
|
16
16
|
This library offers you an easy and high performant caching solution for docker, Kubernetes or Google Cloud Run deployments of Next.js. Read more on how it originated at [TRWK> Case Study](https://trwk.de/case-studies/nextjs-turbo-redis-cache).
|
|
17
17
|
|
|
18
|
+
For a deep dive into the internal architecture (shared hash maps, SyncedMap, request deduplication, and how `get`/`set`/`revalidateTag` work under the hood), see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
19
|
+
|
|
18
20
|
## Compatibility
|
|
19
21
|
|
|
20
|
-
This package is compatible with Next.js 15.0.3 and above
|
|
22
|
+
This package is compatible with Next.js 15.0.3 and above, with the App Router, the Pages Router, or both together in a hybrid app. It is not compatible with Next.js 14.x. or 15-canary.
|
|
21
23
|
Redis Server need to have Redis Server Version 2.8.0 or higher and have to be configured with `notify-keyspace-events` to be able to use the key-space notifications feature.
|
|
22
24
|
|
|
25
|
+
Pages Router support covers ISR pages (`getStaticProps` + `revalidate`, `getStaticPaths` with `fallback`), `notFound: true` and `redirect:` results, and on-demand revalidation via `res.revalidate(path)` — including across multiple server instances sharing one Redis (see the two-instance integration test). App Router and Pages Router entries are handled side by side by the same handler instance, so hybrid apps that use both routers work without any extra configuration.
|
|
26
|
+
|
|
23
27
|
Tested versions are:
|
|
24
28
|
|
|
25
29
|
- Nextjs 15.0.3 + redis client 4.7.0
|
|
26
30
|
- Nextjs 15.2.4 + redis client 4.7.0
|
|
27
31
|
- Nextjs 15.3.2 + redis client 4.7.0
|
|
28
|
-
- Nextjs 15.4.
|
|
29
|
-
- Nextjs 16.0.
|
|
30
|
-
- Nextjs 16.2.
|
|
31
|
-
- Nextjs 16.2.
|
|
32
|
+
- Nextjs 15.4.11 + redis client 4.7.0
|
|
33
|
+
- Nextjs 16.0.11 + redis client 4.7.0 (cacheComponents: false)
|
|
34
|
+
- Nextjs 16.2.6 + redis client 4.7.0 (cacheComponents: false)
|
|
35
|
+
- Nextjs 16.2.6 + redis client 4.7.0 (cacheComponents: true)
|
|
36
|
+
- Nextjs 16.2.6 + redis client 4.7.0 (Pages Router)
|
|
32
37
|
|
|
33
38
|
_Cache Components_ (Next.js 16+) are fully supported. Automated test coverage includes `'use cache'`, `cacheTag`, and `cacheLife` flows in the Cache Components integration suite.
|
|
34
39
|
|
|
@@ -133,27 +138,27 @@ const nextConfig = {
|
|
|
133
138
|
}
|
|
134
139
|
```
|
|
135
140
|
|
|
136
|
-
A working example of above can be found in the `test/
|
|
141
|
+
A working example of above can be found in the `test/nextjs-test-projects/next-app-customized` folder.
|
|
137
142
|
|
|
138
143
|
## Available Options
|
|
139
144
|
|
|
140
|
-
| Option | Description
|
|
141
|
-
| ----------------------------- |
|
|
142
|
-
| redisUrl | Redis connection url
|
|
143
|
-
| database | Redis database number to use. Uses DB 0 for production, DB 1 otherwise
|
|
144
|
-
| keyPrefix | Prefix added to all Redis keys
|
|
145
|
-
| sharedTagsKey | Key used to store shared tags hash map in Redis
|
|
146
|
-
| getTimeoutMs | Timeout in milliseconds for time critical Redis operations. If Redis get is not fulfilled within this time, returns null to avoid blocking site rendering.
|
|
147
|
-
| revalidateTagQuerySize | Number of entries to query in one batch during full sync of shared tags hash map
|
|
148
|
-
| avgResyncIntervalMs | Average interval in milliseconds between tag map full re-syncs
|
|
149
|
-
| redisGetDeduplication | Enable deduplication of Redis get requests via internal in-memory cache.
|
|
150
|
-
| inMemoryCachingTime | Time in milliseconds to cache Redis get results in memory. Set this to 0 to disable in-memory caching completely.
|
|
151
|
-
| defaultStaleAge | Default stale age in seconds for cached items
|
|
152
|
-
| estimateExpireAge | Function to calculate expire age (redis TTL value) from stale age
|
|
153
|
-
| socketOptions | Redis client socket options for TLS/SSL configuration (e.g., `{ tls: true, rejectUnauthorized: false }`)
|
|
154
|
-
| clientOptions | Additional Redis client options (e.g., username, password)
|
|
155
|
-
| killContainerOnErrorThreshold | Number of consecutive errors before the container is killed. Set to 0 to disable.
|
|
156
|
-
| valueSerializer | Pluggable wire-format codec for Redis string values (compression, encryption, custom encoding). See [Custom value serializer](#custom-value-serializer-compression-encryption). | `jsonCacheValueSerializer` (`JSON.stringify` with built-in `Buffer` and `Map` encoding)
|
|
145
|
+
| Option | Description | Default Value |
|
|
146
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
147
|
+
| redisUrl | Redis connection url | `process.env.REDIS_URL? process.env.REDIS_URL : process.env.REDISHOST ? redis://${process.env.REDISHOST}:${process.env.REDISPORT} : 'redis://localhost:6379'` |
|
|
148
|
+
| database | Redis database number to use. Uses DB 0 for production, DB 1 otherwise | `process.env.VERCEL_ENV === 'production' ? 0 : 1` |
|
|
149
|
+
| keyPrefix | Prefix added to all Redis keys | `RedisStringsHandler` default: `process.env.KEY_PREFIX \|\| process.env.VERCEL_URL \|\| 'UNDEFINED_URL_'`<br> Next handlers resolve: `options.keyPrefix \|\| KEY_PREFIX \|\| VERCEL_URL \|\| BUILD_ID \|\| 'UNDEFINED_URL_'` |
|
|
150
|
+
| sharedTagsKey | Key used to store shared tags hash map in Redis | `'__sharedTags__'` |
|
|
151
|
+
| getTimeoutMs | Timeout in milliseconds for time critical Redis operations. If Redis get is not fulfilled within this time, returns null to avoid blocking site rendering. | `process.env.REDIS_COMMAND_TIMEOUT_MS ? (Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 500) : 500` |
|
|
152
|
+
| revalidateTagQuerySize | Number of entries to query in one batch during full sync of shared tags hash map | `250` |
|
|
153
|
+
| avgResyncIntervalMs | Average interval in milliseconds between tag map full re-syncs | `3600000` (1 hour) |
|
|
154
|
+
| redisGetDeduplication | Enable deduplication of Redis get requests via internal in-memory cache. | `true` |
|
|
155
|
+
| inMemoryCachingTime | Time in milliseconds to cache Redis get results in memory. Set this to 0 to disable in-memory caching completely. | `10000` |
|
|
156
|
+
| defaultStaleAge | Default stale age in seconds for cached items | `1209600` (14 days) |
|
|
157
|
+
| estimateExpireAge | Function to calculate expire age (redis TTL value) from stale age | Production: `staleAge * 2`<br> Other: `staleAge * 1.2` |
|
|
158
|
+
| socketOptions | Redis client socket options for TLS/SSL configuration (e.g., `{ tls: true, rejectUnauthorized: false }`) | `{ connectTimeout: timeoutMs }` |
|
|
159
|
+
| clientOptions | Additional Redis client options (e.g., username, password) | `undefined` |
|
|
160
|
+
| killContainerOnErrorThreshold | Number of consecutive errors before the container is killed. Set to 0 to disable. | `Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0 : 0` |
|
|
161
|
+
| valueSerializer | Pluggable wire-format codec for Redis string values (compression, encryption, custom encoding). See [Custom value serializer](#custom-value-serializer-compression-encryption). | `jsonCacheValueSerializer` (`JSON.stringify` with built-in `Buffer` and `Map` encoding) |
|
|
157
162
|
|
|
158
163
|
## Custom value serializer (compression, encryption)
|
|
159
164
|
|
|
@@ -300,7 +305,6 @@ export default class CustomizedCacheHandler {
|
|
|
300
305
|
affects `RedisStringsHandler`. The Next.js 16+ `CacheComponentsHandler` does
|
|
301
306
|
not currently route through `valueSerializer`; that's a candidate follow-up.
|
|
302
307
|
|
|
303
|
-
|
|
304
308
|
## TLS Configuration
|
|
305
309
|
|
|
306
310
|
To connect to Redis using TLS/SSL (e.g., when using Redis over `rediss://` URLs), you can configure the socket options. Here's an example:
|
|
@@ -328,6 +332,8 @@ module.exports = class CustomizedCacheHandler {
|
|
|
328
332
|
|
|
329
333
|
## Consistency of Redis and this caching implementation
|
|
330
334
|
|
|
335
|
+
> For a detailed description of the shared hash maps (`sharedTagsMap`, `revalidatedTagsMap`), the `SyncedMap` Pub/Sub mechanism, and the `DeduplicatedRequestHandler`, see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
336
|
+
|
|
331
337
|
To understand consistency levels of this caching implementation we first have to understand the consistency of redis itself:
|
|
332
338
|
Redis executes commands in a single-threaded manner. This ensures that all operations are processed sequentially, so clients always see a consistent view of the data.
|
|
333
339
|
But depending on the setup of redis this can change:
|
|
@@ -336,23 +342,43 @@ But depending on the setup of redis this can change:
|
|
|
336
342
|
- Eventual consistency: In a master-replica setup (strong consistency only while there is no failover)
|
|
337
343
|
- Eventual consistency: In Redis Cluster mode
|
|
338
344
|
|
|
339
|
-
Consistency levels of Caching Handler
|
|
340
|
-
|
|
345
|
+
### Consistency levels of the Caching Handler
|
|
346
|
+
|
|
347
|
+
Strong consistency is only achievable when **all** of the following conditions are met:
|
|
348
|
+
|
|
349
|
+
1. Redis is used in a single node setup
|
|
350
|
+
2. Only a **single application instance** is running (no cross-instance Pub/Sub propagation delay)
|
|
351
|
+
3. Request Deduplication is turned off (`redisGetDeduplication: false`)
|
|
352
|
+
|
|
353
|
+
In practice, most deployments run multiple application instances, which introduces eventual consistency through two mechanisms:
|
|
354
|
+
|
|
355
|
+
#### Source 1: SyncedMap Pub/Sub propagation (both handlers)
|
|
341
356
|
|
|
342
|
-
|
|
357
|
+
Both `RedisStringsHandler` and `CacheComponentsHandler` maintain in-memory maps (`sharedTagsMap`, `revalidatedTagsMap`) that are synchronized across instances via Redis Pub/Sub (see [SyncedMap in ARCHITECTURE.md](./ARCHITECTURE.md#syncedmap--the-synchronization-primitive)). When a tag is revalidated on instance 1, the Pub/Sub message must propagate to instance 2 before its local maps reflect the change. Until the message arrives, `get()` on instance 2 may still consider a cache entry valid.
|
|
358
|
+
|
|
359
|
+
This is especially relevant for **implicit tags** (`_N_T_` prefix): when `revalidatePath("/products")` is called, the handler records a timestamp in `revalidatedTagsMap` and lazily invalidates nested fetch entries on the next `get()`. If another instance hasn't received the Pub/Sub update yet, it can serve a stale fetch result.
|
|
360
|
+
|
|
361
|
+
The propagation delay is typically in the order of a few milliseconds (Redis Pub/Sub round-trip).
|
|
362
|
+
|
|
363
|
+
#### Source 2: Request Deduplication (both handlers)
|
|
364
|
+
|
|
365
|
+
Both `RedisStringsHandler` and `CacheComponentsHandler` use `DeduplicatedRequestHandler` (enabled by default via `redisGetDeduplication: true`). It caches Redis GET results in memory for `inMemoryCachingTime` (default 10s). The following sequence can occur across instances:
|
|
366
|
+
|
|
367
|
+
```
|
|
343
368
|
Instance 1: call set A 1
|
|
344
369
|
Instance 1: served set A 1
|
|
345
|
-
Instance 2: call get A
|
|
346
|
-
Instance 1: call delete A
|
|
347
|
-
Instance 2: call get A
|
|
348
|
-
Instance 2: served get A
|
|
349
|
-
Instance 2: served get A -> 1 (served 1 but should already be deleted)
|
|
370
|
+
Instance 2: call get A → result cached in dedup cache
|
|
371
|
+
Instance 1: call delete A → revalidateTag deletes from Redis + publishes Pub/Sub delete
|
|
372
|
+
Instance 2: call get A → served from dedup cache (stale!) if Pub/Sub delete hasn't arrived
|
|
373
|
+
Instance 2: served get A → 1 (should already be deleted)
|
|
350
374
|
Instance 1: served delete A
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
When `revalidateTag` / `updateTags` runs, it also deletes the affected keys from the `inMemoryDeduplicationCache` and broadcasts this deletion via Pub/Sub. So the consistency window is bounded by the Pub/Sub propagation time (typically 5–100ms), not the full `inMemoryCachingTime`. Only if the Pub/Sub message is lost would the stale entry persist for the full caching duration.
|
|
351
378
|
|
|
352
|
-
|
|
353
|
-
If using local in-memory caching (Enabled by RedisStringsHandler option inMemoryCachingTime), the window for this eventual consistency to occur can be even higher because additionally also synchronization messages have to get delivered. Depending on your load typically ranging around 50ms to max of 120ms.
|
|
379
|
+
#### Practical impact
|
|
354
380
|
|
|
355
|
-
Since all caching calls
|
|
381
|
+
Since all caching calls within one API/page/server action request are always served by the same instance, this problem will not occur inside a single request but rather in a combination of multiple parallel requests across instances. The probability that this will affect a single user during a request sequence is very low, since typically a single user will not make a follow-up request during this small time window of typically <100ms. To further mitigate the problem and increase performance (increase local in-memory cache hit ratio) make sure that your load balancer will always serve one user to the same instance (sticky sessions).
|
|
356
382
|
|
|
357
383
|
By accepting and tolerating this eventual consistency, the performance of the caching handler is significantly increased.
|
|
358
384
|
|
|
@@ -382,9 +408,9 @@ pnpm test:integration:build-id-prefix
|
|
|
382
408
|
|
|
383
409
|
Folder layout / runners:
|
|
384
410
|
|
|
385
|
-
- **Vitest**
|
|
386
|
-
- **Playwright** (E2E) lives in `
|
|
387
|
-
-
|
|
411
|
+
- **Vitest** unit tests live in `test/vitest/unit/**`; integration tests in `test/vitest/integration/**`.
|
|
412
|
+
- **Playwright** (E2E) lives in `test/playwright/**` (see `playwright.config.ts`).
|
|
413
|
+
- **Test fixtures** (Next.js apps) live in `test/nextjs-test-projects/`.
|
|
388
414
|
|
|
389
415
|
### Unit tests
|
|
390
416
|
|
|
@@ -408,14 +434,6 @@ To run the BUILD_ID integration test independently:
|
|
|
408
434
|
pnpm build && pnpm test:integration:build-id-prefix
|
|
409
435
|
```
|
|
410
436
|
|
|
411
|
-
### E2E tests (Playwright)
|
|
412
|
-
|
|
413
|
-
To run Playwright tests (`tests/**`) you can use:
|
|
414
|
-
|
|
415
|
-
```bash
|
|
416
|
-
pnpm test:e2e
|
|
417
|
-
```
|
|
418
|
-
|
|
419
437
|
The integration tests will start a Next.js server and test the caching handler. You can modify testing behavior by setting the following environment variables:
|
|
420
438
|
|
|
421
439
|
- SKIP_BUILD: If set to true, the integration tests will not build the Next.js app. Therefore the nextjs app needs to be built before running the tests. Or you execute the test once without skip build and the re-execute `pnpm test:integration` with skip build set to true.
|
|
@@ -424,6 +442,14 @@ The integration tests will start a Next.js server and test the caching handler.
|
|
|
424
442
|
|
|
425
443
|
Integration tests may have dependencies between test cases, so individual test failures should be evaluated in the context of the full test suite rather than in isolation.
|
|
426
444
|
|
|
445
|
+
### E2E tests (Playwright)
|
|
446
|
+
|
|
447
|
+
To run Playwright tests (`test/playwright/**`) you can use:
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
pnpm test:e2e
|
|
451
|
+
```
|
|
452
|
+
|
|
427
453
|
## Cache Components handler (Next.js 16+)
|
|
428
454
|
|
|
429
455
|
This package can be used as a Cache Components handler (Node.js runtime) for Next.js apps that enable Cache Components.
|
|
@@ -487,8 +513,8 @@ This repo includes a dedicated Next.js Cache Components integration app with rea
|
|
|
487
513
|
1. Install + start the Cache Components test app:
|
|
488
514
|
|
|
489
515
|
```bash
|
|
490
|
-
pnpm -C test/
|
|
491
|
-
pnpm -C test/
|
|
516
|
+
pnpm -C test/nextjs-test-projects/next-app-16-2-6-cache-components install
|
|
517
|
+
pnpm -C test/nextjs-test-projects/next-app-16-2-6-cache-components dev
|
|
492
518
|
```
|
|
493
519
|
|
|
494
520
|
Then open the Cache Lab pages:
|
|
@@ -516,6 +542,8 @@ app/<segment>/page.tsx files will request using the APP_PAGE kind.
|
|
|
516
542
|
|
|
517
543
|
Fetch requests (inside app route or page) will request using the FETCH kind.
|
|
518
544
|
|
|
545
|
+
For details on how these kinds are handled internally (tag maps, deduplication, value transformation), see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
546
|
+
|
|
519
547
|
## License
|
|
520
548
|
|
|
521
549
|
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
package/dist/index.d.mts
CHANGED
|
@@ -55,6 +55,73 @@ type CacheEntry = {
|
|
|
55
55
|
lastModified: number;
|
|
56
56
|
tags: string[];
|
|
57
57
|
};
|
|
58
|
+
/** Discriminated union of the `ctx` argument Next.js passes to
|
|
59
|
+
* {@link RedisStringsHandler.get}. The Pages Router (`PAGES`) has no PPR
|
|
60
|
+
* concept, so `isRoutePPREnabled` is optional for it.
|
|
61
|
+
*
|
|
62
|
+
* Note: `REDIRECT` is intentionally absent. Next.js always calls `get()` with
|
|
63
|
+
* `kind: 'PAGES'` for Pages Router routes, even when the stored value has
|
|
64
|
+
* `kind: 'REDIRECT'`. The handler returns whatever value is cached and Next.js
|
|
65
|
+
* interprets it accordingly. */
|
|
66
|
+
type GetContext = {
|
|
67
|
+
kind: 'APP_ROUTE' | 'APP_PAGE';
|
|
68
|
+
isRoutePPREnabled: boolean;
|
|
69
|
+
isFallback: boolean;
|
|
70
|
+
} | {
|
|
71
|
+
kind: 'PAGES';
|
|
72
|
+
isRoutePPREnabled?: boolean;
|
|
73
|
+
isFallback: boolean;
|
|
74
|
+
} | {
|
|
75
|
+
kind: 'FETCH';
|
|
76
|
+
revalidate: number;
|
|
77
|
+
fetchUrl: string;
|
|
78
|
+
fetchIdx: number;
|
|
79
|
+
tags: string[];
|
|
80
|
+
softTags: string[];
|
|
81
|
+
isFallback: boolean;
|
|
82
|
+
};
|
|
83
|
+
/** Discriminated union of cache payloads accepted by
|
|
84
|
+
* {@link RedisStringsHandler.set}. `null` is a valid payload: the Pages Router
|
|
85
|
+
* stores `notFound: true` results as a cache entry with a null value. */
|
|
86
|
+
type SetCacheValue = {
|
|
87
|
+
kind: 'APP_PAGE';
|
|
88
|
+
status?: number;
|
|
89
|
+
headers: {
|
|
90
|
+
'x-nextjs-stale-time': string;
|
|
91
|
+
'x-next-cache-tags': string;
|
|
92
|
+
};
|
|
93
|
+
html: string;
|
|
94
|
+
rscData: Buffer;
|
|
95
|
+
segmentData: unknown;
|
|
96
|
+
postboned: unknown;
|
|
97
|
+
} | {
|
|
98
|
+
kind: 'APP_ROUTE';
|
|
99
|
+
status: number;
|
|
100
|
+
headers: {
|
|
101
|
+
'cache-control'?: string;
|
|
102
|
+
'x-nextjs-stale-time': string;
|
|
103
|
+
'x-next-cache-tags': string;
|
|
104
|
+
};
|
|
105
|
+
body: Buffer;
|
|
106
|
+
} | {
|
|
107
|
+
kind: 'PAGES';
|
|
108
|
+
html: string;
|
|
109
|
+
pageData: Record<string, unknown>;
|
|
110
|
+
headers?: Record<string, number | string | string[] | undefined>;
|
|
111
|
+
status?: number;
|
|
112
|
+
} | {
|
|
113
|
+
kind: 'REDIRECT';
|
|
114
|
+
props: Record<string, unknown>;
|
|
115
|
+
} | {
|
|
116
|
+
kind: 'FETCH';
|
|
117
|
+
data: {
|
|
118
|
+
headers: Record<string, string>;
|
|
119
|
+
body: string;
|
|
120
|
+
status: number;
|
|
121
|
+
url: string;
|
|
122
|
+
};
|
|
123
|
+
revalidate: number | false;
|
|
124
|
+
} | null;
|
|
58
125
|
type CreateRedisStringsHandlerOptions = {
|
|
59
126
|
/** Redis redisUrl to use.
|
|
60
127
|
* @default process.env.REDIS_URL? process.env.REDIS_URL : process.env.REDISHOST
|
|
@@ -158,56 +225,15 @@ declare class RedisStringsHandler {
|
|
|
158
225
|
resetRequestCache(): void;
|
|
159
226
|
private clientReadyCalls;
|
|
160
227
|
private assertClientIsReady;
|
|
161
|
-
get(key: string, ctx:
|
|
162
|
-
|
|
163
|
-
isRoutePPREnabled: boolean;
|
|
164
|
-
isFallback: boolean;
|
|
165
|
-
} | {
|
|
166
|
-
kind: 'FETCH';
|
|
167
|
-
revalidate: number;
|
|
168
|
-
fetchUrl: string;
|
|
169
|
-
fetchIdx: number;
|
|
170
|
-
tags: string[];
|
|
171
|
-
softTags: string[];
|
|
172
|
-
isFallback: boolean;
|
|
173
|
-
}): Promise<CacheEntry | null>;
|
|
174
|
-
set(key: string, data: {
|
|
175
|
-
kind: 'APP_PAGE';
|
|
176
|
-
status?: number;
|
|
177
|
-
headers: {
|
|
178
|
-
'x-nextjs-stale-time': string;
|
|
179
|
-
'x-next-cache-tags': string;
|
|
180
|
-
};
|
|
181
|
-
html: string;
|
|
182
|
-
rscData: Buffer;
|
|
183
|
-
segmentData: unknown;
|
|
184
|
-
postboned: unknown;
|
|
185
|
-
} | {
|
|
186
|
-
kind: 'APP_ROUTE';
|
|
187
|
-
status: number;
|
|
188
|
-
headers: {
|
|
189
|
-
'cache-control'?: string;
|
|
190
|
-
'x-nextjs-stale-time': string;
|
|
191
|
-
'x-next-cache-tags': string;
|
|
192
|
-
};
|
|
193
|
-
body: Buffer;
|
|
194
|
-
} | {
|
|
195
|
-
kind: 'FETCH';
|
|
196
|
-
data: {
|
|
197
|
-
headers: Record<string, string>;
|
|
198
|
-
body: string;
|
|
199
|
-
status: number;
|
|
200
|
-
url: string;
|
|
201
|
-
};
|
|
202
|
-
revalidate: number | false;
|
|
203
|
-
}, ctx: {
|
|
228
|
+
get(key: string, ctx: GetContext): Promise<CacheEntry | null>;
|
|
229
|
+
set(key: string, data: SetCacheValue, ctx: {
|
|
204
230
|
isRoutePPREnabled: boolean;
|
|
205
231
|
isFallback: boolean;
|
|
206
232
|
tags?: string[];
|
|
207
233
|
revalidate?: number | false;
|
|
208
234
|
cacheControl?: {
|
|
209
|
-
revalidate:
|
|
210
|
-
expire: undefined;
|
|
235
|
+
revalidate: number | false;
|
|
236
|
+
expire: number | undefined;
|
|
211
237
|
};
|
|
212
238
|
}): Promise<void>;
|
|
213
239
|
revalidateTag(tagOrTags: string | string[], ...rest: any[]): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -55,6 +55,73 @@ type CacheEntry = {
|
|
|
55
55
|
lastModified: number;
|
|
56
56
|
tags: string[];
|
|
57
57
|
};
|
|
58
|
+
/** Discriminated union of the `ctx` argument Next.js passes to
|
|
59
|
+
* {@link RedisStringsHandler.get}. The Pages Router (`PAGES`) has no PPR
|
|
60
|
+
* concept, so `isRoutePPREnabled` is optional for it.
|
|
61
|
+
*
|
|
62
|
+
* Note: `REDIRECT` is intentionally absent. Next.js always calls `get()` with
|
|
63
|
+
* `kind: 'PAGES'` for Pages Router routes, even when the stored value has
|
|
64
|
+
* `kind: 'REDIRECT'`. The handler returns whatever value is cached and Next.js
|
|
65
|
+
* interprets it accordingly. */
|
|
66
|
+
type GetContext = {
|
|
67
|
+
kind: 'APP_ROUTE' | 'APP_PAGE';
|
|
68
|
+
isRoutePPREnabled: boolean;
|
|
69
|
+
isFallback: boolean;
|
|
70
|
+
} | {
|
|
71
|
+
kind: 'PAGES';
|
|
72
|
+
isRoutePPREnabled?: boolean;
|
|
73
|
+
isFallback: boolean;
|
|
74
|
+
} | {
|
|
75
|
+
kind: 'FETCH';
|
|
76
|
+
revalidate: number;
|
|
77
|
+
fetchUrl: string;
|
|
78
|
+
fetchIdx: number;
|
|
79
|
+
tags: string[];
|
|
80
|
+
softTags: string[];
|
|
81
|
+
isFallback: boolean;
|
|
82
|
+
};
|
|
83
|
+
/** Discriminated union of cache payloads accepted by
|
|
84
|
+
* {@link RedisStringsHandler.set}. `null` is a valid payload: the Pages Router
|
|
85
|
+
* stores `notFound: true` results as a cache entry with a null value. */
|
|
86
|
+
type SetCacheValue = {
|
|
87
|
+
kind: 'APP_PAGE';
|
|
88
|
+
status?: number;
|
|
89
|
+
headers: {
|
|
90
|
+
'x-nextjs-stale-time': string;
|
|
91
|
+
'x-next-cache-tags': string;
|
|
92
|
+
};
|
|
93
|
+
html: string;
|
|
94
|
+
rscData: Buffer;
|
|
95
|
+
segmentData: unknown;
|
|
96
|
+
postboned: unknown;
|
|
97
|
+
} | {
|
|
98
|
+
kind: 'APP_ROUTE';
|
|
99
|
+
status: number;
|
|
100
|
+
headers: {
|
|
101
|
+
'cache-control'?: string;
|
|
102
|
+
'x-nextjs-stale-time': string;
|
|
103
|
+
'x-next-cache-tags': string;
|
|
104
|
+
};
|
|
105
|
+
body: Buffer;
|
|
106
|
+
} | {
|
|
107
|
+
kind: 'PAGES';
|
|
108
|
+
html: string;
|
|
109
|
+
pageData: Record<string, unknown>;
|
|
110
|
+
headers?: Record<string, number | string | string[] | undefined>;
|
|
111
|
+
status?: number;
|
|
112
|
+
} | {
|
|
113
|
+
kind: 'REDIRECT';
|
|
114
|
+
props: Record<string, unknown>;
|
|
115
|
+
} | {
|
|
116
|
+
kind: 'FETCH';
|
|
117
|
+
data: {
|
|
118
|
+
headers: Record<string, string>;
|
|
119
|
+
body: string;
|
|
120
|
+
status: number;
|
|
121
|
+
url: string;
|
|
122
|
+
};
|
|
123
|
+
revalidate: number | false;
|
|
124
|
+
} | null;
|
|
58
125
|
type CreateRedisStringsHandlerOptions = {
|
|
59
126
|
/** Redis redisUrl to use.
|
|
60
127
|
* @default process.env.REDIS_URL? process.env.REDIS_URL : process.env.REDISHOST
|
|
@@ -158,56 +225,15 @@ declare class RedisStringsHandler {
|
|
|
158
225
|
resetRequestCache(): void;
|
|
159
226
|
private clientReadyCalls;
|
|
160
227
|
private assertClientIsReady;
|
|
161
|
-
get(key: string, ctx:
|
|
162
|
-
|
|
163
|
-
isRoutePPREnabled: boolean;
|
|
164
|
-
isFallback: boolean;
|
|
165
|
-
} | {
|
|
166
|
-
kind: 'FETCH';
|
|
167
|
-
revalidate: number;
|
|
168
|
-
fetchUrl: string;
|
|
169
|
-
fetchIdx: number;
|
|
170
|
-
tags: string[];
|
|
171
|
-
softTags: string[];
|
|
172
|
-
isFallback: boolean;
|
|
173
|
-
}): Promise<CacheEntry | null>;
|
|
174
|
-
set(key: string, data: {
|
|
175
|
-
kind: 'APP_PAGE';
|
|
176
|
-
status?: number;
|
|
177
|
-
headers: {
|
|
178
|
-
'x-nextjs-stale-time': string;
|
|
179
|
-
'x-next-cache-tags': string;
|
|
180
|
-
};
|
|
181
|
-
html: string;
|
|
182
|
-
rscData: Buffer;
|
|
183
|
-
segmentData: unknown;
|
|
184
|
-
postboned: unknown;
|
|
185
|
-
} | {
|
|
186
|
-
kind: 'APP_ROUTE';
|
|
187
|
-
status: number;
|
|
188
|
-
headers: {
|
|
189
|
-
'cache-control'?: string;
|
|
190
|
-
'x-nextjs-stale-time': string;
|
|
191
|
-
'x-next-cache-tags': string;
|
|
192
|
-
};
|
|
193
|
-
body: Buffer;
|
|
194
|
-
} | {
|
|
195
|
-
kind: 'FETCH';
|
|
196
|
-
data: {
|
|
197
|
-
headers: Record<string, string>;
|
|
198
|
-
body: string;
|
|
199
|
-
status: number;
|
|
200
|
-
url: string;
|
|
201
|
-
};
|
|
202
|
-
revalidate: number | false;
|
|
203
|
-
}, ctx: {
|
|
228
|
+
get(key: string, ctx: GetContext): Promise<CacheEntry | null>;
|
|
229
|
+
set(key: string, data: SetCacheValue, ctx: {
|
|
204
230
|
isRoutePPREnabled: boolean;
|
|
205
231
|
isFallback: boolean;
|
|
206
232
|
tags?: string[];
|
|
207
233
|
revalidate?: number | false;
|
|
208
234
|
cacheControl?: {
|
|
209
|
-
revalidate:
|
|
210
|
-
expire: undefined;
|
|
235
|
+
revalidate: number | false;
|
|
236
|
+
expire: number | undefined;
|
|
211
237
|
};
|
|
212
238
|
}): Promise<void>;
|
|
213
239
|
revalidateTag(tagOrTags: string | string[], ...rest: any[]): Promise<void>;
|