@trieb.work/nextjs-turbo-redis-cache 1.18.0 → 1.18.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/dist/index.js +27 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +27 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -1
- package/.cursor/skills/testing/SKILL.md +0 -121
- package/.github/workflows/ci.yml +0 -309
- package/.github/workflows/pages.yml +0 -38
- package/.github/workflows/release.yml +0 -58
- package/.prettierrc.json +0 -6
- package/.vscode/settings.json +0 -10
- package/ARCHITECTURE.md +0 -470
- package/commitlint.config.cjs +0 -4
- package/docker/redis.conf +0 -1
- package/docs/index.html +0 -140
- package/docs/robots.txt +0 -4
- package/docs/sitemap.xml +0 -8
- package/docs/styles.css +0 -141
- package/eslint.config.mjs +0 -26
- package/hooks/commit-msg +0 -10
- package/hooks/pre-commit +0 -11
- package/playwright.config.ts +0 -18
- package/release.config.cjs +0 -12
- package/scripts/prepare.sh +0 -7
- package/scripts/setup-git.sh +0 -8
- package/scripts/setup-repo-name.sh +0 -17
- package/scripts/vitest-run-staged.cjs +0 -44
- package/src/CacheComponentsHandler.ts +0 -597
- package/src/CachedHandler.ts +0 -53
- package/src/DeduplicatedRequestHandler.ts +0 -110
- package/src/RedisStringsHandler.ts +0 -949
- package/src/SyncedMap.ts +0 -496
- package/src/ZodHandler.ts +0 -45
- package/src/index.ts +0 -23
- package/src/serializer.ts +0 -59
- package/src/utils/cacheTtl.ts +0 -71
- package/src/utils/compareAndUnlink.ts +0 -29
- package/src/utils/debug.ts +0 -30
- package/src/utils/json.ts +0 -49
- package/src/utils/prefix.ts +0 -44
- package/src/utils/redisConnection.ts +0 -17
- package/src/utils/tagRevalidation.ts +0 -162
- package/test/README.md +0 -194
- package/test/nextjs-test-projects/next-app-15-4-11/README.md +0 -36
- package/test/nextjs-test-projects/next-app-15-4-11/eslint.config.mjs +0 -16
- package/test/nextjs-test-projects/next-app-15-4-11/next.config.js +0 -9
- package/test/nextjs-test-projects/next-app-15-4-11/package-lock.json +0 -5969
- package/test/nextjs-test-projects/next-app-15-4-11/package.json +0 -29
- package/test/nextjs-test-projects/next-app-15-4-11/pnpm-lock.yaml +0 -3726
- package/test/nextjs-test-projects/next-app-15-4-11/postcss.config.mjs +0 -5
- package/test/nextjs-test-projects/next-app-15-4-11/public/file.svg +0 -1
- package/test/nextjs-test-projects/next-app-15-4-11/public/globe.svg +0 -1
- package/test/nextjs-test-projects/next-app-15-4-11/public/next.svg +0 -1
- package/test/nextjs-test-projects/next-app-15-4-11/public/vercel.svg +0 -1
- package/test/nextjs-test-projects/next-app-15-4-11/public/window.svg +0 -1
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/revalidatePath/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/revalidateTag/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/layout.tsx +0 -59
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-15-4-11/tsconfig.json +0 -27
- package/test/nextjs-test-projects/next-app-16-0-11/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-0-11/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-0-11/next.config.ts +0 -10
- package/test/nextjs-test-projects/next-app-16-0-11/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-0-11/pnpm-lock.yaml +0 -4163
- package/test/nextjs-test-projects/next-app-16-0-11/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/revalidatePath/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/revalidateTag/route.ts +0 -20
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/layout.tsx +0 -59
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/update-tag-test/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-0-11/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/cache-handler.js +0 -3
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/next.config.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/pnpm-lock.yaml +0 -4206
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/file.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/globe.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/next.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/vercel.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/window.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/expire-matrix/revalidate/route.ts +0 -34
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/expire-matrix/route.ts +0 -23
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/page.tsx +0 -112
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/revalidate-durations/page.tsx +0 -106
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/use-cache-remote/page.tsx +0 -85
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/layout.tsx +0 -57
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/update-tag-test/page.tsx +0 -22
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-16-2-6/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-2-6/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-2-6/next.config.ts +0 -7
- package/test/nextjs-test-projects/next-app-16-2-6/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-2-6/pnpm-lock.yaml +0 -4165
- package/test/nextjs-test-projects/next-app-16-2-6/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidatePath/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidateTag/route.ts +0 -20
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/layout.tsx +0 -59
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/update-tag-test/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-2-6/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/cache-handler.js +0 -3
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/next.config.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/pnpm-lock.yaml +0 -4165
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/file.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/globe.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/next.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/vercel.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/window.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/expire-matrix/revalidate/route.ts +0 -34
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/expire-matrix/route.ts +0 -23
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/page.tsx +0 -112
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/revalidate-durations/page.tsx +0 -106
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/use-cache-remote/page.tsx +0 -85
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/layout.tsx +0 -57
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/update-tag-test/page.tsx +0 -22
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-16-3-0/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-3-0/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-3-0/next.config.ts +0 -7
- package/test/nextjs-test-projects/next-app-16-3-0/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-3-0/pnpm-lock.yaml +0 -4284
- package/test/nextjs-test-projects/next-app-16-3-0/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidatePath/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidateTag/route.ts +0 -20
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/layout.tsx +0 -59
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/update-tag-test/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-3-0/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/cache-handler.js +0 -3
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/next.config.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/pnpm-lock.yaml +0 -4284
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/file.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/globe.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/next.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/vercel.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/window.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/expire-matrix/revalidate/route.ts +0 -34
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/expire-matrix/route.ts +0 -23
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/page.tsx +0 -112
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/revalidate-durations/page.tsx +0 -106
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/use-cache-remote/page.tsx +0 -85
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/layout.tsx +0 -57
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/update-tag-test/page.tsx +0 -22
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-customized/README.md +0 -36
- package/test/nextjs-test-projects/next-app-customized/customized-cache-handler.js +0 -34
- package/test/nextjs-test-projects/next-app-customized/eslint.config.mjs +0 -16
- package/test/nextjs-test-projects/next-app-customized/next.config.js +0 -6
- package/test/nextjs-test-projects/next-pages-16-2-6/README.md +0 -16
- package/test/nextjs-test-projects/next-pages-16-2-6/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-pages-16-2-6/next.config.ts +0 -7
- package/test/nextjs-test-projects/next-pages-16-2-6/package.json +0 -26
- package/test/nextjs-test-projects/next-pages-16-2-6/pnpm-lock.yaml +0 -3896
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/api/revalidate.ts +0 -24
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/index.tsx +0 -11
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/isr/[slug].tsx +0 -49
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/static-forever.tsx +0 -20
- package/test/nextjs-test-projects/next-pages-16-2-6/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-pages-16-3-0/README.md +0 -16
- package/test/nextjs-test-projects/next-pages-16-3-0/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-pages-16-3-0/next.config.ts +0 -7
- package/test/nextjs-test-projects/next-pages-16-3-0/package.json +0 -26
- package/test/nextjs-test-projects/next-pages-16-3-0/pnpm-lock.yaml +0 -3939
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/api/revalidate.ts +0 -24
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/index.tsx +0 -11
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/isr/[slug].tsx +0 -49
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/static-forever.tsx +0 -20
- package/test/nextjs-test-projects/next-pages-16-3-0/tsconfig.json +0 -34
- package/test/playwright/cache-lab.spec.ts +0 -232
- package/test/playwright/update-tag.spec.ts +0 -33
- package/test/vitest/integration/build-id-prefix.integration.test.ts +0 -102
- package/test/vitest/integration/cache-components/cache-components.integration.test.ts +0 -389
- package/test/vitest/integration/cache-components/redis-kill-reconnect.test.ts +0 -65
- package/test/vitest/integration/cache-components/redis-quit-vs-disconnect.test.ts +0 -59
- package/test/vitest/integration/cache-components/redis-subscriber-outage.test.ts +0 -100
- package/test/vitest/integration/cache-components/scripts/redis-kill-reconnect.ts +0 -243
- package/test/vitest/integration/cache-components/scripts/redis-quit-vs-disconnect.ts +0 -113
- package/test/vitest/integration/cache-components/scripts/redis-subscriber-outage.ts +0 -379
- package/test/vitest/integration/cache-components/scripts/redis-test-helpers.ts +0 -155
- package/test/vitest/integration/nextjs-cache-handler.integration.test.ts +0 -959
- package/test/vitest/integration/pages-router.integration.test.ts +0 -430
- package/test/vitest/unit/SyncedMap-orphan-cleanup.test.ts +0 -111
- package/test/vitest/unit/SyncedMap-reconnect-repro.test.ts +0 -154
- package/test/vitest/unit/cache-components-update-tags.test.ts +0 -350
- package/test/vitest/unit/cache-ttl.test.ts +0 -67
- package/test/vitest/unit/compare-and-unlink.test.ts +0 -22
- package/test/vitest/unit/index.test.ts +0 -190
- package/test/vitest/unit/pages-router-kinds.test.ts +0 -303
- package/test/vitest/unit/reconnect-socket-already-opened.test.ts +0 -117
- package/test/vitest/unit/redis-connection.test.ts +0 -39
- package/test/vitest/unit/serializer.test.ts +0 -182
- package/test/vitest/unit/tag-revalidation.test.ts +0 -178
- package/test/vitest/unit/utils/prefix.test.ts +0 -115
- package/tsconfig.json +0 -18
- package/tsup.config.ts +0 -14
- package/vite.config.ts +0 -34
- package/vitest.cache-components.config.ts +0 -16
package/ARCHITECTURE.md
DELETED
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
# Architecture – Cache Handler Logic
|
|
2
|
-
|
|
3
|
-
This document describes the internal architecture of the cache handler, focusing on the three core functions (`get`, `set`, `revalidateTag` / `updateTags`), the shared hash maps, and the supporting infrastructure (`SyncedMap`, `DeduplicatedRequestHandler`).
|
|
4
|
-
|
|
5
|
-
Two handler implementations exist side by side:
|
|
6
|
-
|
|
7
|
-
| Handler | Next.js API | File |
|
|
8
|
-
| -------------------------- | ------------------------------------------------------ | ------------------------------- |
|
|
9
|
-
| **RedisStringsHandler** | Legacy `cacheHandler` (Next.js 15) | `src/RedisStringsHandler.ts` |
|
|
10
|
-
| **CacheComponentsHandler** | `cacheHandlers.default` (Next.js 16+ Cache Components) | `src/CacheComponentsHandler.ts` |
|
|
11
|
-
|
|
12
|
-
Both follow the same fundamental pattern – store serialized data in Redis strings, maintain two in-memory hash maps for tags, and synchronize those maps across instances – but differ in the data shapes they receive from Next.js and in some performance optimizations.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Table of Contents
|
|
17
|
-
|
|
18
|
-
1. [High-Level Overview](#high-level-overview)
|
|
19
|
-
2. [Shared Hash Maps & Why They Exist](#shared-hash-maps--why-they-exist)
|
|
20
|
-
3. [SyncedMap – The Synchronization Primitive](#syncedmap--the-synchronization-primitive)
|
|
21
|
-
4. [DeduplicatedRequestHandler](#deduplicatedrequesthandler)
|
|
22
|
-
5. [Core Function: `get`](#core-function-get)
|
|
23
|
-
6. [Core Function: `set`](#core-function-set)
|
|
24
|
-
7. [Core Function: `revalidateTag` / `updateTags`](#core-function-revalidatetag--updatetags)
|
|
25
|
-
8. [RedisStringsHandler vs CacheComponentsHandler](#redisstringshandler-vs-cachecomponentshandler)
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## High-Level Overview
|
|
30
|
-
|
|
31
|
-
```mermaid
|
|
32
|
-
flowchart TB
|
|
33
|
-
subgraph "Next.js Runtime"
|
|
34
|
-
NX["Next.js Page / Route / Fetch / 'use cache'"]
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
subgraph "Cache Handler Instance (per Node.js process)"
|
|
38
|
-
GET["get()"]
|
|
39
|
-
SET["set()"]
|
|
40
|
-
REV["revalidateTag() / updateTags()"]
|
|
41
|
-
|
|
42
|
-
subgraph "In-Memory Maps"
|
|
43
|
-
STM["sharedTagsMap\n(SyncedMap<string[]>)\ncacheKey → tags"]
|
|
44
|
-
RTM["revalidatedTagsMap\n(SyncedMap<number>)\ntag → timestamp"]
|
|
45
|
-
DEDUP["inMemoryDeduplicationCache\n(SyncedMap<Promise>)"]
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
subgraph "Redis"
|
|
50
|
-
RS["Redis Strings\n(cached page/fetch data)"]
|
|
51
|
-
RH1["Redis Hash\n__sharedTags__"]
|
|
52
|
-
RH2["Redis Hash\n__revalidated_tags__"]
|
|
53
|
-
PUBSUB["Pub/Sub Channels\n(sync messages)"]
|
|
54
|
-
KSN["Keyspace Notifications\n(expired / evicted)"]
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
NX -->|"get(key, ctx)"| GET
|
|
58
|
-
NX -->|"set(key, data, ctx)"| SET
|
|
59
|
-
NX -->|"revalidateTag(tags)"| REV
|
|
60
|
-
|
|
61
|
-
GET <-->|"GET key"| RS
|
|
62
|
-
SET -->|"SET key EX ttl"| RS
|
|
63
|
-
REV -->|"UNLINK keys"| RS
|
|
64
|
-
|
|
65
|
-
STM <-->|"HSET / HDEL / HSCAN"| RH1
|
|
66
|
-
RTM <-->|"HSET / HDEL / HSCAN"| RH2
|
|
67
|
-
STM <-->|"PUBLISH / SUBSCRIBE"| PUBSUB
|
|
68
|
-
RTM <-->|"PUBLISH / SUBSCRIBE"| PUBSUB
|
|
69
|
-
KSN -->|"evicted / expired"| STM
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## Shared Hash Maps & Why They Exist
|
|
75
|
-
|
|
76
|
-
### The Problem
|
|
77
|
-
|
|
78
|
-
Next.js calls `revalidateTag("product")` with **only the tag name**. It does **not** provide the list of cache keys that belong to that tag.
|
|
79
|
-
|
|
80
|
-
At the same time:
|
|
81
|
-
|
|
82
|
-
- Redis strings (the actual cache entries) are keyed by a cache key (e.g. `/products/[id]`), not by tag.
|
|
83
|
-
- Redis has no native secondary index that maps a tag to all keys that carry it.
|
|
84
|
-
|
|
85
|
-
Without an additional data structure, the only way to find all keys for a tag would be a `KEYS *` or `SCAN` over the entire keyspace, parsing every entry – far too expensive at scale.
|
|
86
|
-
|
|
87
|
-
### The Solution: Two SyncedMaps
|
|
88
|
-
|
|
89
|
-
| Map | Key | Value | Purpose |
|
|
90
|
-
| -------------------- | ------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
-
| `sharedTagsMap` | cache key (e.g. `/products/42`) | `string[]` of tags | Reverse index: given a tag, iterate this map to find all affected cache keys |
|
|
92
|
-
| `revalidatedTagsMap` | tag name (e.g. `product`) | timestamp or `{ stale, expired }` | Tracks _when_ a tag was last revalidated. ISR uses a number and lazy-checks it in `get()`. Cache Components stores `{ stale, expired }` matching Next.js `tagsManifest`; `get()` applies `areTagsExpired` / `areTagsStale`, and `getExpiration()` returns max `expired` |
|
|
93
|
-
|
|
94
|
-
Both maps live **in-memory** in every Node.js process and are **synchronized across instances** through Redis Hash + Pub/Sub (see [SyncedMap](#syncedmap--the-synchronization-primitive)).
|
|
95
|
-
|
|
96
|
-
```mermaid
|
|
97
|
-
flowchart LR
|
|
98
|
-
subgraph "sharedTagsMap"
|
|
99
|
-
K1["/products/42"] -->|"tags"| V1["['product', '_N_T_/products']"]
|
|
100
|
-
K2["/products/99"] -->|"tags"| V2["['product', '_N_T_/products']"]
|
|
101
|
-
K3["/blog/hello"] -->|"tags"| V3["['blog', '_N_T_/blog']"]
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
subgraph "revalidatedTagsMap"
|
|
105
|
-
T1["_N_T_/products"] -->|"timestamp"| TS1["1716123456789"]
|
|
106
|
-
end
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
When `revalidateTag("product")` is called, the handler iterates `sharedTagsMap` to find `/products/42` and `/products/99`, then batch-deletes them from Redis in a single `UNLINK` call.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## SyncedMap – The Synchronization Primitive
|
|
114
|
-
|
|
115
|
-
`SyncedMap<V>` (`src/SyncedMap.ts`) wraps a standard `Map<string, V>` and keeps it synchronized across all Node.js processes via three mechanisms:
|
|
116
|
-
|
|
117
|
-
### 1. Redis Hash (persistent state)
|
|
118
|
-
|
|
119
|
-
Every `set()` writes to both the local map **and** a Redis Hash (`HSET`). On startup, `initialSync()` uses `HSCAN` to load the full hash into memory. A periodic re-sync (default ~1 hour, jittered) guards against drift.
|
|
120
|
-
|
|
121
|
-
### 2. Pub/Sub (real-time cross-instance sync)
|
|
122
|
-
|
|
123
|
-
Every `set()` and `delete()` also publishes a message on a dedicated channel. All other instances subscribe and apply the change to their local map immediately. This avoids the need for polling.
|
|
124
|
-
|
|
125
|
-
```mermaid
|
|
126
|
-
sequenceDiagram
|
|
127
|
-
participant I1 as Instance 1
|
|
128
|
-
participant Redis as Redis
|
|
129
|
-
participant I2 as Instance 2
|
|
130
|
-
|
|
131
|
-
I1->>Redis: HSET __sharedTags__ "/p/42" '["product"]'
|
|
132
|
-
I1->>Redis: PUBLISH sync-channel {type:"insert", key:"/p/42", value:["product"]}
|
|
133
|
-
Redis-->>I2: message on sync-channel
|
|
134
|
-
I2->>I2: map.set("/p/42", ["product"])
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### 3. Keyspace Notifications (eviction / expiry cleanup)
|
|
138
|
-
|
|
139
|
-
When Redis evicts or expires a cache key, the corresponding entry must be removed from the `sharedTagsMap` as well – otherwise the map would grow indefinitely. `SyncedMap` subscribes to `__keyevent@<db>__:evicted` and `__keyevent@<db>__:expired` and automatically deletes matching entries.
|
|
140
|
-
|
|
141
|
-
This requires Redis to be configured with `notify-keyspace-events Exe`.
|
|
142
|
-
|
|
143
|
-
```mermaid
|
|
144
|
-
flowchart LR
|
|
145
|
-
REDIS["Redis expires key\n/products/42"] -->|"keyevent notification"| SM["SyncedMap\ndeletes '/products/42'\nfrom local map + hash"]
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Why not store tags inside the Redis string value?
|
|
149
|
-
|
|
150
|
-
The tag information _is_ stored inside the cached entry value as well. However, during `revalidateTag`, the handler must find _all_ keys that have a given tag without deserializing every cache entry. The in-memory `sharedTagsMap` enables O(n) iteration over a lightweight map instead of O(n) deserialization of potentially large page payloads.
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## DeduplicatedRequestHandler
|
|
155
|
-
|
|
156
|
-
`DeduplicatedRequestHandler<T, K>` (`src/DeduplicatedRequestHandler.ts`) is a generic request deduplication + short-lived in-memory cache wrapper. It is used by **both** `RedisStringsHandler` and `CacheComponentsHandler`.
|
|
157
|
-
|
|
158
|
-
### Motivation
|
|
159
|
-
|
|
160
|
-
In a single Next.js request, the same cache key can be read multiple times (e.g. layout + page + multiple fetch calls referencing the same data). Without deduplication, each `get()` would issue a separate `Redis GET`. With deduplication:
|
|
161
|
-
|
|
162
|
-
1. The first call creates the Redis request promise and stores it in the `inMemoryDeduplicationCache`.
|
|
163
|
-
2. Subsequent calls for the same key within the caching window (default 10s) return the stored promise.
|
|
164
|
-
3. After the caching timeout, the entry is evicted.
|
|
165
|
-
|
|
166
|
-
### Seed on Set
|
|
167
|
-
|
|
168
|
-
When `set()` stores a value in Redis, it also **seeds** the deduplication cache with the serialized value. This means an immediately following `get()` is served from memory without hitting Redis at all.
|
|
169
|
-
|
|
170
|
-
```mermaid
|
|
171
|
-
sequenceDiagram
|
|
172
|
-
participant NX as Next.js
|
|
173
|
-
participant H as Cache Handler (either)
|
|
174
|
-
participant DC as DeduplicationCache
|
|
175
|
-
participant R as Redis
|
|
176
|
-
|
|
177
|
-
NX->>H: get("key-A")
|
|
178
|
-
H->>DC: has("key-A")? No
|
|
179
|
-
H->>R: GET "prefix:key-A"
|
|
180
|
-
H->>DC: set("key-A", promise)
|
|
181
|
-
R-->>H: serialized value
|
|
182
|
-
H-->>NX: parsed CacheEntry
|
|
183
|
-
|
|
184
|
-
NX->>H: get("key-A") (same or different request within 10s)
|
|
185
|
-
H->>DC: has("key-A")? Yes
|
|
186
|
-
DC-->>H: cached promise
|
|
187
|
-
H-->>NX: structuredClone of result
|
|
188
|
-
|
|
189
|
-
Note over DC: After 10s timeout
|
|
190
|
-
DC->>DC: delete("key-A")
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
The `inMemoryDeduplicationCache` itself is a `SyncedMap` configured with `withoutRedisHashmap: true` and `withoutSetSync: true` – it only uses the Pub/Sub delete channel so that revalidations on other instances can evict stale entries.
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## Core Function: `get`
|
|
198
|
-
|
|
199
|
-
### What Next.js Passes In
|
|
200
|
-
|
|
201
|
-
**RedisStringsHandler:**
|
|
202
|
-
|
|
203
|
-
```typescript
|
|
204
|
-
get(key: string, ctx: {
|
|
205
|
-
kind: 'APP_ROUTE' | 'APP_PAGE' | 'FETCH';
|
|
206
|
-
tags?: string[]; // explicit tags (FETCH only)
|
|
207
|
-
softTags?: string[]; // implicit tags like _N_T_/path (FETCH only)
|
|
208
|
-
revalidate?: number; // FETCH only
|
|
209
|
-
fetchUrl?: string; // FETCH only
|
|
210
|
-
isFallback: boolean;
|
|
211
|
-
})
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
**CacheComponentsHandler:**
|
|
215
|
-
|
|
216
|
-
```typescript
|
|
217
|
-
get(cacheKey: string, softTags: string[])
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
The Cache Components interface is simpler: it receives only the cache key and soft tags. Soft-tag staleness for implicit `_N_T_` tags is handled by Next.js via `getExpiration()`. Explicit `cacheTag()`s are checked in `get()` (`areTagsExpired` / `areTagsStale`), matching `DefaultCacheHandler.get()`.
|
|
221
|
-
|
|
222
|
-
### What `get` Does
|
|
223
|
-
|
|
224
|
-
```mermaid
|
|
225
|
-
flowchart TD
|
|
226
|
-
A["get(key, ctx)"] --> B["Redis GET prefix:key"]
|
|
227
|
-
B --> C{Result found?}
|
|
228
|
-
C -->|No| RN["Return null/undefined"]
|
|
229
|
-
C -->|Yes| D["JSON.parse result"]
|
|
230
|
-
|
|
231
|
-
D --> CC{CacheComponents?}
|
|
232
|
-
CC -->|Yes| EXP{"entry.expire TTL elapsed?"}
|
|
233
|
-
EXP -->|Yes| DEL1["UNLINK key\nDelete from sharedTagsMap\nReturn undefined"]
|
|
234
|
-
EXP -->|No| TAGX{"areTagsExpired(entry.tags)?"}
|
|
235
|
-
TAGX -->|Yes| DEL1
|
|
236
|
-
TAGX -->|No| TAGS{"areTagsStale(entry.tags)?"}
|
|
237
|
-
TAGS -->|Yes| SETR["Set revalidate = -1"]
|
|
238
|
-
TAGS -->|No| H["Return cache entry"]
|
|
239
|
-
SETR --> H
|
|
240
|
-
|
|
241
|
-
CC -->|No / ISR| E["Check revalidatedTagsMap\nfor all tags + softTags"]
|
|
242
|
-
E --> F{Any tag revalidated\nafter entry.lastModified/timestamp?}
|
|
243
|
-
F -->|Yes| G["UNLINK key from Redis\nDelete from sharedTagsMap\nReturn null/undefined"]
|
|
244
|
-
F -->|No| H
|
|
245
|
-
|
|
246
|
-
H --> H2["RedisStringsHandler:\nreturn { value, lastModified, tags }"]
|
|
247
|
-
H --> H3["CacheComponentsHandler:\nConvert base64 → Uint8Array → ReadableStream\nreturn { value, tags, stale, timestamp, expire, revalidate }"]
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Key Details
|
|
251
|
-
|
|
252
|
-
1. **Timeout**: Every `Redis GET` uses `AbortSignal.timeout(getTimeoutMs)` (default 500ms). If Redis is slow, the handler returns `null` so the page can be server-rendered instead of waiting.
|
|
253
|
-
|
|
254
|
-
2. **Deduplication** (both handlers): Before hitting Redis, the deduplication cache is checked for an existing in-flight or recently resolved promise for the same key. Enabled by default (`redisGetDeduplication: true`) with a 10s caching window (`inMemoryCachingTime: 10_000`).
|
|
255
|
-
|
|
256
|
-
3. **Lazy tag invalidation**: Instead of eagerly deleting all fetch entries when a page tag is revalidated, `RedisStringsHandler` records the revalidation timestamp in `revalidatedTagsMap`. During `get`, it compares `lastModified` against the max revalidation timestamp of all related tags. If the entry is stale, it is deleted and `null` is returned. This is necessary because `revalidateTag` for implicit tags (`_N_T_` prefix) does not know which fetch cache keys are affected. **Cache Components** matches Next.js `DefaultCacheHandler.get()`: `areTagsExpired` → miss, `areTagsStale` → return the entry with `revalidate: -1` (SWR). Next.js uses `getExpiration()` only for implicit/soft tags.
|
|
257
|
-
|
|
258
|
-
4. **Value transformation** (CacheComponentsHandler): The stored value is a base64-encoded string (from a `ReadableStream<Uint8Array>`). On read, it is decoded back to `Uint8Array` and wrapped in a new `ReadableStream`.
|
|
259
|
-
|
|
260
|
-
---
|
|
261
|
-
|
|
262
|
-
## Core Function: `set`
|
|
263
|
-
|
|
264
|
-
### What Next.js Passes In
|
|
265
|
-
|
|
266
|
-
**RedisStringsHandler:**
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
set(key: string, data: {
|
|
270
|
-
kind: 'APP_PAGE' | 'APP_ROUTE' | 'FETCH';
|
|
271
|
-
// APP_PAGE: { html, rscData, headers: { 'x-next-cache-tags', 'x-nextjs-stale-time' } }
|
|
272
|
-
// APP_ROUTE: { body, status, headers: { 'x-next-cache-tags', 'cache-control' } }
|
|
273
|
-
// FETCH: { data: { headers, body, status, url }, revalidate }
|
|
274
|
-
}, ctx: {
|
|
275
|
-
tags?: string[];
|
|
276
|
-
revalidate?: number | false;
|
|
277
|
-
cacheControl?: { revalidate: number; expire: number };
|
|
278
|
-
})
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
**CacheComponentsHandler:**
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
set(cacheKey: string, pendingEntry: Promise<{
|
|
285
|
-
value: ReadableStream<Uint8Array>;
|
|
286
|
-
tags: string[];
|
|
287
|
-
stale: number;
|
|
288
|
-
timestamp: number;
|
|
289
|
-
expire: number;
|
|
290
|
-
revalidate: number;
|
|
291
|
-
}>)
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
Note that the Cache Components handler receives a **Promise** of the entry – the value is not yet available when `set` is called.
|
|
295
|
-
|
|
296
|
-
### What `set` Does
|
|
297
|
-
|
|
298
|
-
```mermaid
|
|
299
|
-
flowchart TD
|
|
300
|
-
A["set(key, data, ctx)"] --> B["Construct CacheEntry"]
|
|
301
|
-
|
|
302
|
-
B --> B2["RedisStringsHandler:\nExtract tags from headers (APP_PAGE/APP_ROUTE)\nMerge with ctx.tags\nWrap in { value: data, lastModified: Date.now(), tags }"]
|
|
303
|
-
B --> B3["CacheComponentsHandler:\nAwait pendingEntry promise\nTee the ReadableStream (don't mutate original)\nConvert stream → Uint8Array → base64 string"]
|
|
304
|
-
|
|
305
|
-
B2 --> C["JSON.stringify(cacheEntry)"]
|
|
306
|
-
B3 --> C
|
|
307
|
-
|
|
308
|
-
C --> D["Calculate TTL"]
|
|
309
|
-
D --> D2["RedisStringsHandler:\nresolveCacheEntryTtlSeconds()\nprefer cacheControl.expire,\nlegacy fallback to estimateExpireAge(revalidate)"]
|
|
310
|
-
D --> D3["CacheComponentsHandler:\nentry.expire (already in seconds)"]
|
|
311
|
-
|
|
312
|
-
D2 --> E["Redis SET prefix:key serialized EX ttl"]
|
|
313
|
-
D3 --> E
|
|
314
|
-
|
|
315
|
-
C --> SEED["Seed deduplication cache\n(if redisGetDeduplication enabled)"]
|
|
316
|
-
|
|
317
|
-
E --> F{Tags changed?}
|
|
318
|
-
F -->|"Yes (or new)"| G["sharedTagsMap.set(key, tags)\n→ HSET + PUBLISH"]
|
|
319
|
-
F -->|"No change"| H["Skip tag update"]
|
|
320
|
-
|
|
321
|
-
G --> I["Done"]
|
|
322
|
-
H --> I
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### Key Details
|
|
326
|
-
|
|
327
|
-
1. **Tag extraction** (RedisStringsHandler): For `APP_PAGE` and `APP_ROUTE`, tags are encoded in the `x-next-cache-tags` header as a comma-separated string. The handler splits this and merges with `ctx.tags`.
|
|
328
|
-
|
|
329
|
-
2. **Tag deduplication**: Before writing to `sharedTagsMap`, both handlers check if the current tags are identical to the already stored tags. If so, the write is skipped to reduce Redis operations.
|
|
330
|
-
|
|
331
|
-
3. **Dedup cache seeding** (both handlers): The serialized value is immediately seeded into the `DeduplicatedRequestHandler`, so a following `get()` for the same key can be served from memory.
|
|
332
|
-
|
|
333
|
-
4. **Stream handling** (CacheComponentsHandler): The `ReadableStream` from Next.js is tee'd – one branch is consumed to produce the stored base64 value, while the original stream is left intact for Next.js to continue using.
|
|
334
|
-
|
|
335
|
-
5. **Parallel operations**: The Redis `SET` and the `sharedTagsMap.set()` run concurrently via `Promise.all`.
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
## Core Function: `revalidateTag` / `updateTags`
|
|
340
|
-
|
|
341
|
-
### What Next.js Passes In
|
|
342
|
-
|
|
343
|
-
**RedisStringsHandler:**
|
|
344
|
-
|
|
345
|
-
```typescript
|
|
346
|
-
revalidateTag(tagOrTags: string | string[])
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
**CacheComponentsHandler:**
|
|
350
|
-
|
|
351
|
-
```typescript
|
|
352
|
-
updateTags(tags: string[], durations?: { expire?: number })
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
In both cases, the handler receives **only tag names** – no cache keys.
|
|
356
|
-
|
|
357
|
-
### What `revalidateTag` / `updateTags` Does
|
|
358
|
-
|
|
359
|
-
```mermaid
|
|
360
|
-
flowchart TD
|
|
361
|
-
A["revalidateTag / updateTags"] --> B["Normalize tags to Set"]
|
|
362
|
-
B --> ISR{Cache Components?}
|
|
363
|
-
|
|
364
|
-
ISR -->|No ISR| C["Persist Date.now() in revalidatedTagsMap"]
|
|
365
|
-
C --> D["Scan sharedTagsMap for matching keys"]
|
|
366
|
-
D --> E{keysToDelete empty?}
|
|
367
|
-
E -->|Yes| F["Return early"]
|
|
368
|
-
E -->|No| G["UNLINK matching Redis keys"]
|
|
369
|
-
G --> H["Delete from sharedTagsMap + Pub/Sub"]
|
|
370
|
-
G --> I["Delete from inMemoryDeduplicationCache"]
|
|
371
|
-
H --> J["Done"]
|
|
372
|
-
I --> J
|
|
373
|
-
|
|
374
|
-
ISR -->|Yes| K{"durations provided?"}
|
|
375
|
-
K -->|No updateTag / deprecated revalidateTag| L["Write { expired: now }"]
|
|
376
|
-
K -->|Yes| M["Write stale = now"]
|
|
377
|
-
M --> N{"durations.expire defined?"}
|
|
378
|
-
N -->|Yes| O["expired = now + expire * 1000"]
|
|
379
|
-
N -->|No| P["keep prior expired"]
|
|
380
|
-
L --> Q["Persist in revalidatedTagsMap\nno UNLINK"]
|
|
381
|
-
O --> Q2["expire: 0 → expired=now blocking miss\nexpire>0 → SWR window"]
|
|
382
|
-
P --> Q
|
|
383
|
-
Q2 --> Q
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
### Key Details
|
|
387
|
-
|
|
388
|
-
1. **Implicit tags (`_N_T_` prefix)**: When Next.js calls `revalidatePath("/products")`, it internally translates this to `revalidateTag("_N_T_/products")`. The handler cannot know which _fetch_ cache keys are nested inside that page. Therefore, it only records the timestamp in `revalidatedTagsMap`. The actual cleanup happens lazily in `get()` when the fetch entry is next accessed.
|
|
389
|
-
|
|
390
|
-
2. **SWR `updateTags` (Cache Components)**: Matches Next.js `DefaultCacheHandler.updateTags()` (`default.js` in 16.0.11, 16.2.6, 16.3.0) and the [revalidateTag docs](https://nextjs.org/docs/app/api-reference/functions/revalidateTag). `revalidateTag(tag, profile)` looks up `cacheLife[profile].expire` (seconds) and calls `updateTags(tags, { expire })`. The handler never unlinks in `updateTags`:
|
|
391
|
-
|
|
392
|
-
- No `durations` (`updateTag` / deprecated single-arg `revalidateTag`): `{ expired: now }` — next `get()` is a hard miss.
|
|
393
|
-
- `{ expire: 0 }`: `{ stale: now, expired: now }` — blocking miss, same HTTP effect.
|
|
394
|
-
- `{ expire: N }` including `'max'` (~1 year) and `'default'` (`INFINITE_CACHE`): `{ stale: now, expired: now + N * 1000 }`. `get()` returns the entry with `revalidate: -1` until `expired`. Past `expired`, `areTagsExpired` is a miss.
|
|
395
|
-
|
|
396
|
-
3. **Batch deletion (ISR only)**: `RedisStringsHandler.revalidateTag` deletes matching Redis keys in a single `UNLINK`. Cache Components leaves keys in place; `get()` drops them only after `areTagsExpired`.
|
|
397
|
-
|
|
398
|
-
4. **Cross-instance propagation**: Tag-manifest writes publish via Pub/Sub, so all instances see `{ stale, expired }` without waiting for `refreshTags()`.
|
|
399
|
-
|
|
400
|
-
5. **Preset expire seconds** (from Next.js `config-shared.js`): `seconds` 60, `minutes` 3600, `hours` 86400, `days` 604800, `weeks` 2592000, `max` 31536000, `default` 4294967294 (`0xfffffffe`).
|
|
401
|
-
|
|
402
|
-
### Reviewer notes (automated review false positives)
|
|
403
|
-
|
|
404
|
-
**Legacy plain numbers in `__cacheComponents_revalidated_tags__`**
|
|
405
|
-
|
|
406
|
-
Next.js always calls `updateTags(tags, durations?)` with `{ stale, expired }` semantics. It does not write plain numbers into the cache handler. Plain numbers in Redis are a rolling-upgrade artifact from older package versions that stored `Date.now()` directly. A _future-looking_ plain number is not an SWR `expired` window (those always include `stale`); it only arises from cross-instance clock skew. `normalizeTagManifest()` clamps legacy plain numbers to `Math.min(stored, now)` on read so slower instances do not delay invalidation.
|
|
407
|
-
|
|
408
|
-
**`resolveCacheEntryTtlSeconds()` returning `undefined`**
|
|
409
|
-
|
|
410
|
-
Returning `undefined` when both `cacheControl.expire` and `revalidate` are absent is deliberate: Redis keys are written without `EX` and invalidation relies on tags. `revalidate: false` is handled separately and maps to `defaultStaleAge` (see `cache-ttl.test.ts` and Pages Router `/static-forever` integration). Next.js supplies `revalidate: false` for fully static pages in practice.
|
|
411
|
-
|
|
412
|
-
**Tag-manifest keys vs `SyncedMap` orphan cleanup**
|
|
413
|
-
|
|
414
|
-
`cleanupKeysNotInRedis()` SCANs Redis string keys and HDELs hash fields whose names are missing from that set. That is correct for `sharedTagsMap` (cache key → tags). It is wrong for `revalidatedTagsMap` (tag name → `{ stale, expired }`): tag names are never top-level keys, so startup or the hourly resync would wipe every invalidation and publish the delete fleet-wide. Tag-manifest maps set `customizedSync.withoutOrphanCleanup`.
|
|
415
|
-
|
|
416
|
-
**Dedup cache vs Redis UNLINK**
|
|
417
|
-
|
|
418
|
-
`updateTags` no longer deletes Redis entries (Next.js `DefaultCacheHandler` does not either). A 10s in-memory GET cache can still hold the pre-invalidation payload. `get()` must not `UNLINK` a Redis value that differs from the payload it just judged expired — that would delete a replacement written by another instance. Expired reads bypass dedup, compare-and-delete only the same serialized value, and `updateTags` evicts matching dedup entries.
|
|
419
|
-
|
|
420
|
-
**Rolling upgrades and `{ stale, expired }` objects**
|
|
421
|
-
|
|
422
|
-
Older package versions stored a number and compared it numerically. Immediate hard-expires (`expired <= now` and no newer `stale`) are still persisted as that number so mixed fleets invalidate. A later stale-only update (`updateTags(tags, {})`) that outruns a past `expired` stays an object. SWR windows stay `{ stale, expired }` objects; instances that cannot parse them keep serving until TTL. Prefer a coordinated rollout (or a unique `keyPrefix`) when using `revalidateTag(tag, profile)` SWR.
|
|
423
|
-
|
|
424
|
-
---
|
|
425
|
-
|
|
426
|
-
## RedisStringsHandler vs CacheComponentsHandler
|
|
427
|
-
|
|
428
|
-
| Aspect | RedisStringsHandler | CacheComponentsHandler |
|
|
429
|
-
| ------------------------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
430
|
-
| **Next.js version** | 15+ (legacy `cacheHandler`) | 16+ (`cacheHandlers.default`) |
|
|
431
|
-
| **Cache kinds** | `APP_PAGE`, `APP_ROUTE`, `FETCH` | Unified (all via `'use cache'`, `cacheTag`, `cacheLife`) |
|
|
432
|
-
| **Value format** | Arbitrary JSON (page HTML, RSC data, fetch response) | `ReadableStream<Uint8Array>` ↔ base64 string |
|
|
433
|
-
| **Entry shape** | `{ value, lastModified, tags }` | `{ value, tags, stale, timestamp, expire, revalidate }` |
|
|
434
|
-
| **set() receives** | Resolved data | `Promise<CacheComponentsEntry>` (may not yet be resolved) |
|
|
435
|
-
| **TTL calculation** | `resolveCacheEntryTtlSeconds()` – prefers `cacheControl.expire`, legacy `estimateExpireAge(revalidate)` fallback | `entry.expire` – passed directly by Next.js |
|
|
436
|
-
| **Tag source in set** | `x-next-cache-tags` header + `ctx.tags` | `entry.tags` |
|
|
437
|
-
| **Request deduplication** | Yes (`DeduplicatedRequestHandler`, default on) | Yes (`DeduplicatedRequestHandler`, default on) |
|
|
438
|
-
| **In-memory caching** | Yes (configurable `inMemoryCachingTime`, default 10s) | Yes (configurable `inMemoryCachingTime`, default 10s) |
|
|
439
|
-
| **Revalidation function** | `revalidateTag(tagOrTags)` | `updateTags(tags, durations?)` |
|
|
440
|
-
| **Implicit tag handling** | Stores timestamp in `revalidatedTagsMap`, lazy check in `get()` for `FETCH` kind | Stores `{ stale, expired }` in `revalidatedTagsMap`; `get()` applies `areTagsExpired` / `areTagsStale`; `getExpiration()` returns max `expired` (soft tags) |
|
|
441
|
-
| **Singleton pattern** | External (user wraps in `module.exports`) | Built-in `getRedisCacheComponentsHandler()` singleton |
|
|
442
|
-
| **Key prefix resolution** | `keyPrefix` option or `KEY_PREFIX` / `VERCEL_URL` env | `resolveKeyPrefix()` with BUILD_ID fallback |
|
|
443
|
-
|
|
444
|
-
### Shared Architecture
|
|
445
|
-
|
|
446
|
-
Despite the API differences, the core invalidation architecture is identical:
|
|
447
|
-
|
|
448
|
-
```mermaid
|
|
449
|
-
flowchart LR
|
|
450
|
-
subgraph "Both Handlers"
|
|
451
|
-
direction TB
|
|
452
|
-
A["Redis Strings\n(actual cache data)"]
|
|
453
|
-
B["sharedTagsMap\n(key → tags)"]
|
|
454
|
-
C["revalidatedTagsMap\n(tag → timestamp)"]
|
|
455
|
-
D["inMemoryDeduplicationCache\n(key → Promise)"]
|
|
456
|
-
end
|
|
457
|
-
|
|
458
|
-
SET["set()"] --> A
|
|
459
|
-
SET --> B
|
|
460
|
-
SET -.->|"seed"| D
|
|
461
|
-
GET["get()"] --> D
|
|
462
|
-
GET -->|"on miss"| A
|
|
463
|
-
GET -.->|"ISR: check staleness"| C
|
|
464
|
-
EXP["getExpiration()"] -->|"CC: timestamp now"| C
|
|
465
|
-
REV["revalidateTag()\nupdateTags()"] --> C
|
|
466
|
-
REV -->|"evict"| D
|
|
467
|
-
GET -->|"expired + same value: UNLINK"| A
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
Both handlers rely on `SyncedMap` for cross-instance consistency of the tag maps and use the same pattern of "find affected keys via `sharedTagsMap` → batch delete from Redis → clean up maps". Both also use `DeduplicatedRequestHandler` (enabled by default) to reduce Redis load by deduplicating concurrent `get()` calls for the same key and seeding the cache on `set()`.
|
package/commitlint.config.cjs
DELETED
package/docker/redis.conf
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
notify-keyspace-events Exe
|
package/docs/index.html
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
<title>nextjs-turbo-redis-cache | High-Performance Redis Cache Handler for Next.js</title>
|
|
7
|
-
<meta
|
|
8
|
-
name="description"
|
|
9
|
-
content="High-performance Redis cache handler for Next.js 15/16 (App Router and Pages Router). Get request deduplication, batch tag invalidation, in-memory caching, and production-ready scalability."
|
|
10
|
-
/>
|
|
11
|
-
<meta
|
|
12
|
-
name="keywords"
|
|
13
|
-
content="Next.js cache handler, Redis cache, App Router cache, Pages Router cache, Next.js performance, cache components, batch tag invalidation"
|
|
14
|
-
/>
|
|
15
|
-
<meta name="robots" content="index, follow" />
|
|
16
|
-
<meta name="author" content="TRWK" />
|
|
17
|
-
<link rel="canonical" href="https://trieb-work.github.io/nextjs-turbo-redis-cache/" />
|
|
18
|
-
|
|
19
|
-
<meta property="og:type" content="website" />
|
|
20
|
-
<meta property="og:title" content="nextjs-turbo-redis-cache | High-Performance Redis Cache Handler for Next.js" />
|
|
21
|
-
<meta
|
|
22
|
-
property="og:description"
|
|
23
|
-
content="The production-ready Redis cache handler for Next.js 15/16 with request deduplication, in-memory caching, and efficient tag invalidation."
|
|
24
|
-
/>
|
|
25
|
-
<meta property="og:url" content="https://trieb-work.github.io/nextjs-turbo-redis-cache/" />
|
|
26
|
-
<meta
|
|
27
|
-
property="og:image"
|
|
28
|
-
content="https://github.com/user-attachments/assets/4103191e-4f4d-4139-a519-0b5bfab3e8b4"
|
|
29
|
-
/>
|
|
30
|
-
|
|
31
|
-
<meta name="twitter:card" content="summary_large_image" />
|
|
32
|
-
<meta name="twitter:title" content="nextjs-turbo-redis-cache" />
|
|
33
|
-
<meta
|
|
34
|
-
name="twitter:description"
|
|
35
|
-
content="Production-ready Redis cache handler for Next.js 15/16 App Router and Pages Router workloads."
|
|
36
|
-
/>
|
|
37
|
-
<meta
|
|
38
|
-
name="twitter:image"
|
|
39
|
-
content="https://github.com/user-attachments/assets/4103191e-4f4d-4139-a519-0b5bfab3e8b4"
|
|
40
|
-
/>
|
|
41
|
-
|
|
42
|
-
<link rel="stylesheet" href="./styles.css" />
|
|
43
|
-
|
|
44
|
-
<script type="application/ld+json">
|
|
45
|
-
{
|
|
46
|
-
"@context": "https://schema.org",
|
|
47
|
-
"@type": "SoftwareSourceCode",
|
|
48
|
-
"name": "nextjs-turbo-redis-cache",
|
|
49
|
-
"description": "High-performance Redis cache handler for Next.js 15/16 App Router and Pages Router with request deduplication, in-memory caching, and batch tag invalidation.",
|
|
50
|
-
"codeRepository": "https://github.com/trieb-work/nextjs-turbo-redis-cache",
|
|
51
|
-
"programmingLanguage": "TypeScript",
|
|
52
|
-
"runtimePlatform": "Node.js",
|
|
53
|
-
"license": "https://opensource.org/license/mit",
|
|
54
|
-
"author": {
|
|
55
|
-
"@type": "Organization",
|
|
56
|
-
"name": "TRWK",
|
|
57
|
-
"url": "https://trwk.de"
|
|
58
|
-
},
|
|
59
|
-
"keywords": [
|
|
60
|
-
"Next.js",
|
|
61
|
-
"Redis",
|
|
62
|
-
"Cache Handler",
|
|
63
|
-
"App Router",
|
|
64
|
-
"Pages Router",
|
|
65
|
-
"Performance"
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
</script>
|
|
69
|
-
</head>
|
|
70
|
-
<body>
|
|
71
|
-
<header class="hero">
|
|
72
|
-
<p class="eyebrow">Open-source · MIT Licensed</p>
|
|
73
|
-
<h1>Redis Cache Handler Built for High-Traffic Next.js Apps</h1>
|
|
74
|
-
<p class="lead">
|
|
75
|
-
nextjs-turbo-redis-cache is a production-ready cache handler for Next.js 15/16 (App Router and Pages Router), designed for
|
|
76
|
-
speed, consistency tradeoff awareness, and scale.
|
|
77
|
-
</p>
|
|
78
|
-
<div class="actions">
|
|
79
|
-
<a class="button primary" href="https://www.npmjs.com/package/@trieb.work/nextjs-turbo-redis-cache">Install from npm</a>
|
|
80
|
-
<a class="button" href="https://github.com/trieb-work/nextjs-turbo-redis-cache">View on GitHub</a>
|
|
81
|
-
</div>
|
|
82
|
-
</header>
|
|
83
|
-
|
|
84
|
-
<main>
|
|
85
|
-
<section aria-labelledby="features-title">
|
|
86
|
-
<h2 id="features-title">Why teams use nextjs-turbo-redis-cache</h2>
|
|
87
|
-
<ul class="feature-list">
|
|
88
|
-
<li>
|
|
89
|
-
<h3>Batch tag invalidation</h3>
|
|
90
|
-
<p>Groups and optimizes delete operations to reduce Redis load during revalidation spikes.</p>
|
|
91
|
-
</li>
|
|
92
|
-
<li>
|
|
93
|
-
<h3>Request deduplication</h3>
|
|
94
|
-
<p>Prevents duplicate Redis reads in hot paths to lower latency and improve throughput.</p>
|
|
95
|
-
</li>
|
|
96
|
-
<li>
|
|
97
|
-
<h3>In-memory acceleration</h3>
|
|
98
|
-
<p>Local memory caching reduces repetitive lookups and keeps frequent reads fast.</p>
|
|
99
|
-
</li>
|
|
100
|
-
<li>
|
|
101
|
-
<h3>Cache Components support</h3>
|
|
102
|
-
<p>Supports Next.js 16+ Cache Components flows including use cache, cacheTag, and cacheLife.</p>
|
|
103
|
-
</li>
|
|
104
|
-
</ul>
|
|
105
|
-
</section>
|
|
106
|
-
|
|
107
|
-
<section aria-labelledby="quickstart-title">
|
|
108
|
-
<h2 id="quickstart-title">Quick start</h2>
|
|
109
|
-
<p>Install the package and wire it as your cache handler in a Next.js project (App Router, Pages Router, or both in a hybrid app).</p>
|
|
110
|
-
<pre><code>pnpm add @trieb.work/nextjs-turbo-redis-cache</code></pre>
|
|
111
|
-
<p>Then follow the setup guide in the README for configuration, Redis environment variables, and advanced options.</p>
|
|
112
|
-
</section>
|
|
113
|
-
|
|
114
|
-
<section aria-labelledby="links-title" class="links">
|
|
115
|
-
<h2 id="links-title">Resources</h2>
|
|
116
|
-
<ul>
|
|
117
|
-
<li><a href="https://github.com/trieb-work/nextjs-turbo-redis-cache">GitHub Repository</a></li>
|
|
118
|
-
<li><a href="https://www.npmjs.com/package/@trieb.work/nextjs-turbo-redis-cache">npm Package</a></li>
|
|
119
|
-
<li><a href="https://trwk.de/case-studies/nextjs-turbo-redis-cache">TRWK Case Study</a></li>
|
|
120
|
-
</ul>
|
|
121
|
-
</section>
|
|
122
|
-
|
|
123
|
-
<section aria-labelledby="alternatives-title" class="links">
|
|
124
|
-
<h2 id="alternatives-title">Other cache handler projects</h2>
|
|
125
|
-
<p>Compare this project with other open-source handlers used in the Next.js ecosystem:</p>
|
|
126
|
-
<ul>
|
|
127
|
-
<li><a href="https://github.com/fortedigital/nextjs-cache-handler">fortedigital/nextjs-cache-handler</a></li>
|
|
128
|
-
<li><a href="https://github.com/mrjasonroy/cache-components-cache-handler">mrjasonroy/cache-components-cache-handler</a></li>
|
|
129
|
-
<li><a href="https://github.com/leejpsd/nextjs-cache-handler">leejpsd/nextjs-cache-handler</a></li>
|
|
130
|
-
<li><a href="https://github.com/caching-tools/next-shared-cache">caching-tools/next-shared-cache (@neshca/cache-handler)</a></li>
|
|
131
|
-
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cache-handler-redis">vercel/next.js cache-handler-redis example</a></li>
|
|
132
|
-
</ul>
|
|
133
|
-
</section>
|
|
134
|
-
</main>
|
|
135
|
-
|
|
136
|
-
<footer>
|
|
137
|
-
<p>Maintained by <a href="https://trwk.de">TRWK</a>.</p>
|
|
138
|
-
</footer>
|
|
139
|
-
</body>
|
|
140
|
-
</html>
|
package/docs/robots.txt
DELETED
package/docs/sitemap.xml
DELETED