@trieb.work/nextjs-turbo-redis-cache 1.14.1 → 1.15.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/.cursor/skills/testing/SKILL.md +121 -0
- package/.github/workflows/ci.yml +155 -70
- package/.github/workflows/pages.yml +38 -0
- package/ARCHITECTURE.md +431 -0
- package/CHANGELOG.md +19 -0
- package/README.md +220 -48
- package/dist/index.d.mts +77 -2
- package/dist/index.d.ts +77 -2
- package/dist/index.js +109 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +106 -23
- package/dist/index.mjs.map +1 -1
- package/docs/index.html +139 -0
- package/docs/robots.txt +4 -0
- package/docs/sitemap.xml +8 -0
- package/docs/styles.css +141 -0
- package/eslint.config.mjs +1 -1
- package/package.json +16 -18
- package/playwright.config.ts +5 -3
- package/src/CacheComponentsHandler.ts +57 -2
- package/src/RedisStringsHandler.ts +48 -11
- package/src/SyncedMap.ts +62 -13
- package/src/index.ts +4 -0
- package/src/serializer.ts +59 -0
- package/test/README.md +179 -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-0-11-cache-components/src/app/api/cached-with-cachelife/route.ts +24 -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/{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} +46 -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 +17 -10
- package/test/vitest/unit/SyncedMap-reconnect-repro.test.ts +154 -0
- package/{src → test/vitest/unit}/index.test.ts +3 -3
- package/test/{cache-components/reconnect-socket-already-opened.spec.ts → vitest/unit/reconnect-socket-already-opened.test.ts} +1 -1
- package/test/vitest/unit/serializer.test.ts +182 -0
- 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-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/docs/index.html
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
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. 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, 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 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 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
|
+
"Performance"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
</head>
|
|
69
|
+
<body>
|
|
70
|
+
<header class="hero">
|
|
71
|
+
<p class="eyebrow">Open-source · MIT Licensed</p>
|
|
72
|
+
<h1>Redis Cache Handler Built for High-Traffic Next.js Apps</h1>
|
|
73
|
+
<p class="lead">
|
|
74
|
+
nextjs-turbo-redis-cache is a production-ready cache handler for Next.js 15/16 App Router, designed for
|
|
75
|
+
speed, consistency tradeoff awareness, and scale.
|
|
76
|
+
</p>
|
|
77
|
+
<div class="actions">
|
|
78
|
+
<a class="button primary" href="https://www.npmjs.com/package/@trieb.work/nextjs-turbo-redis-cache">Install from npm</a>
|
|
79
|
+
<a class="button" href="https://github.com/trieb-work/nextjs-turbo-redis-cache">View on GitHub</a>
|
|
80
|
+
</div>
|
|
81
|
+
</header>
|
|
82
|
+
|
|
83
|
+
<main>
|
|
84
|
+
<section aria-labelledby="features-title">
|
|
85
|
+
<h2 id="features-title">Why teams use nextjs-turbo-redis-cache</h2>
|
|
86
|
+
<ul class="feature-list">
|
|
87
|
+
<li>
|
|
88
|
+
<h3>Batch tag invalidation</h3>
|
|
89
|
+
<p>Groups and optimizes delete operations to reduce Redis load during revalidation spikes.</p>
|
|
90
|
+
</li>
|
|
91
|
+
<li>
|
|
92
|
+
<h3>Request deduplication</h3>
|
|
93
|
+
<p>Prevents duplicate Redis reads in hot paths to lower latency and improve throughput.</p>
|
|
94
|
+
</li>
|
|
95
|
+
<li>
|
|
96
|
+
<h3>In-memory acceleration</h3>
|
|
97
|
+
<p>Local memory caching reduces repetitive lookups and keeps frequent reads fast.</p>
|
|
98
|
+
</li>
|
|
99
|
+
<li>
|
|
100
|
+
<h3>Cache Components support</h3>
|
|
101
|
+
<p>Supports Next.js 16+ Cache Components flows including use cache, cacheTag, and cacheLife.</p>
|
|
102
|
+
</li>
|
|
103
|
+
</ul>
|
|
104
|
+
</section>
|
|
105
|
+
|
|
106
|
+
<section aria-labelledby="quickstart-title">
|
|
107
|
+
<h2 id="quickstart-title">Quick start</h2>
|
|
108
|
+
<p>Install the package and wire it as your cache handler in a Next.js App Router project.</p>
|
|
109
|
+
<pre><code>pnpm add @trieb.work/nextjs-turbo-redis-cache</code></pre>
|
|
110
|
+
<p>Then follow the setup guide in the README for configuration, Redis environment variables, and advanced options.</p>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<section aria-labelledby="links-title" class="links">
|
|
114
|
+
<h2 id="links-title">Resources</h2>
|
|
115
|
+
<ul>
|
|
116
|
+
<li><a href="https://github.com/trieb-work/nextjs-turbo-redis-cache">GitHub Repository</a></li>
|
|
117
|
+
<li><a href="https://www.npmjs.com/package/@trieb.work/nextjs-turbo-redis-cache">npm Package</a></li>
|
|
118
|
+
<li><a href="https://trwk.de/case-studies/nextjs-turbo-redis-cache">TRWK Case Study</a></li>
|
|
119
|
+
</ul>
|
|
120
|
+
</section>
|
|
121
|
+
|
|
122
|
+
<section aria-labelledby="alternatives-title" class="links">
|
|
123
|
+
<h2 id="alternatives-title">Other cache handler projects</h2>
|
|
124
|
+
<p>Compare this project with other open-source handlers used in the Next.js ecosystem:</p>
|
|
125
|
+
<ul>
|
|
126
|
+
<li><a href="https://github.com/fortedigital/nextjs-cache-handler">fortedigital/nextjs-cache-handler</a></li>
|
|
127
|
+
<li><a href="https://github.com/mrjasonroy/cache-components-cache-handler">mrjasonroy/cache-components-cache-handler</a></li>
|
|
128
|
+
<li><a href="https://github.com/leejpsd/nextjs-cache-handler">leejpsd/nextjs-cache-handler</a></li>
|
|
129
|
+
<li><a href="https://github.com/caching-tools/next-shared-cache">caching-tools/next-shared-cache (@neshca/cache-handler)</a></li>
|
|
130
|
+
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cache-handler-redis">vercel/next.js cache-handler-redis example</a></li>
|
|
131
|
+
</ul>
|
|
132
|
+
</section>
|
|
133
|
+
</main>
|
|
134
|
+
|
|
135
|
+
<footer>
|
|
136
|
+
<p>Maintained by <a href="https://trwk.de">TRWK</a>.</p>
|
|
137
|
+
</footer>
|
|
138
|
+
</body>
|
|
139
|
+
</html>
|
package/docs/robots.txt
ADDED
package/docs/sitemap.xml
ADDED
package/docs/styles.css
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #0a1628;
|
|
3
|
+
--bg-alt: #0f2139;
|
|
4
|
+
--card: #112a49;
|
|
5
|
+
--text: #e8f1ff;
|
|
6
|
+
--muted: #b8c8de;
|
|
7
|
+
--accent: #56c2ff;
|
|
8
|
+
--accent-strong: #1fa6f3;
|
|
9
|
+
--border: #2a476b;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
* {
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
margin: 0;
|
|
18
|
+
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
|
19
|
+
background: radial-gradient(circle at top, var(--bg-alt), var(--bg));
|
|
20
|
+
color: var(--text);
|
|
21
|
+
line-height: 1.6;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.hero,
|
|
25
|
+
main,
|
|
26
|
+
footer {
|
|
27
|
+
width: min(1080px, 92vw);
|
|
28
|
+
margin: 0 auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.hero {
|
|
32
|
+
padding: 4.5rem 0 2.2rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.eyebrow {
|
|
36
|
+
color: var(--accent);
|
|
37
|
+
font-weight: 700;
|
|
38
|
+
text-transform: uppercase;
|
|
39
|
+
letter-spacing: 0.06em;
|
|
40
|
+
font-size: 0.82rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
h1 {
|
|
44
|
+
font-size: clamp(2rem, 4vw, 3.2rem);
|
|
45
|
+
line-height: 1.15;
|
|
46
|
+
margin: 0.5rem 0 1rem;
|
|
47
|
+
max-width: 18ch;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.lead {
|
|
51
|
+
color: var(--muted);
|
|
52
|
+
font-size: 1.1rem;
|
|
53
|
+
max-width: 64ch;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.actions {
|
|
57
|
+
display: flex;
|
|
58
|
+
gap: 0.8rem;
|
|
59
|
+
flex-wrap: wrap;
|
|
60
|
+
margin-top: 1.5rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.button {
|
|
64
|
+
display: inline-block;
|
|
65
|
+
text-decoration: none;
|
|
66
|
+
color: var(--text);
|
|
67
|
+
border: 1px solid var(--border);
|
|
68
|
+
padding: 0.75rem 1rem;
|
|
69
|
+
border-radius: 0.7rem;
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.button.primary {
|
|
74
|
+
background: var(--accent-strong);
|
|
75
|
+
border-color: var(--accent-strong);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
main {
|
|
79
|
+
padding: 1.5rem 0 3rem;
|
|
80
|
+
display: grid;
|
|
81
|
+
gap: 1.2rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
section {
|
|
85
|
+
background: color-mix(in srgb, var(--card) 82%, black);
|
|
86
|
+
border: 1px solid var(--border);
|
|
87
|
+
border-radius: 1rem;
|
|
88
|
+
padding: 1.2rem;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
h2 {
|
|
92
|
+
margin-top: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.feature-list {
|
|
96
|
+
margin: 0;
|
|
97
|
+
padding-left: 1rem;
|
|
98
|
+
display: grid;
|
|
99
|
+
gap: 0.75rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.feature-list li {
|
|
103
|
+
list-style: square;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.feature-list h3 {
|
|
107
|
+
margin-bottom: 0.2rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.feature-list p,
|
|
111
|
+
.links li,
|
|
112
|
+
footer p {
|
|
113
|
+
color: var(--muted);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pre {
|
|
117
|
+
overflow-x: auto;
|
|
118
|
+
background: #081323;
|
|
119
|
+
border: 1px solid var(--border);
|
|
120
|
+
border-radius: 0.7rem;
|
|
121
|
+
padding: 0.8rem;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
a {
|
|
125
|
+
color: var(--accent);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
footer {
|
|
129
|
+
padding: 0 0 2rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@media (max-width: 700px) {
|
|
133
|
+
.hero {
|
|
134
|
+
padding-top: 2.8rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.button {
|
|
138
|
+
width: 100%;
|
|
139
|
+
text-align: center;
|
|
140
|
+
}
|
|
141
|
+
}
|
package/eslint.config.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trieb.work/nextjs-turbo-redis-cache",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"homepage": "https://trwk.de/case-studies/nextjs-turbo-redis-cache",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/trieb-work/nextjs-turbo-redis-cache.git"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"dev": "pnpm test",
|
|
11
|
-
"run-dev-server": "pnpm run-dev-server-15-4-7",
|
|
12
|
-
"run-dev-server-15-3-2": "cd ./test/integration/next-app-15-3-2 && pnpm dev",
|
|
13
|
-
"run-dev-server-15-0-3": "cd ./test/integration/next-app-15-0-3 && pnpm dev",
|
|
14
|
-
"run-dev-server-15-4-7": "cd ./test/integration/next-app-15-4-7 && pnpm dev",
|
|
10
|
+
"dev": "pnpm test:unit",
|
|
15
11
|
"build": "tsup",
|
|
16
12
|
"lint": "eslint -c eslint.config.mjs --fix",
|
|
17
13
|
"format": "prettier --write 'src/**/*.ts' 'src/*.ts'",
|
|
18
|
-
"test": "vitest --coverage --config vite.config.ts",
|
|
19
|
-
"test:ci": "vitest --run --coverage --config vite.config.ts src/**/*.test.ts src/**/*.test.tsx test/integration/nextjs-cache-handler.integration.test.ts",
|
|
20
|
-
"test:integration:build-id-prefix": "vitest --run --coverage --config vite.config.ts test/integration/build-id-prefix.integration.test.ts",
|
|
21
|
-
"test:ui": "vitest --ui --config vite.config.ts",
|
|
22
|
-
"test:unit": "vitest --config vite.config.ts src/**/*.test.ts src/**/*.test.tsx",
|
|
23
|
-
"test:integration": "vitest --config vite.config.ts ./test/integration/nextjs-cache-handler.integration.test.ts",
|
|
24
|
-
"test:cache-components": "vitest --run --config vitest.cache-components.config.ts",
|
|
25
14
|
"prepare": "./scripts/prepare.sh",
|
|
26
|
-
"test
|
|
27
|
-
"test:
|
|
15
|
+
"test": "vitest --coverage --config vite.config.ts",
|
|
16
|
+
"test:ui": "vitest --ui --config vite.config.ts",
|
|
17
|
+
"test:unit": "vitest --run --config vite.config.ts test/vitest/unit",
|
|
18
|
+
"test:unit:watch": "vitest --config vite.config.ts test/vitest/unit",
|
|
19
|
+
"test:unit:coverage": "vitest --run --coverage --config vite.config.ts test/vitest/unit",
|
|
20
|
+
"test:integration": "vitest --run --config vite.config.ts test/vitest/integration/nextjs-cache-handler.integration.test.ts",
|
|
21
|
+
"test:integration:build-id-prefix": "vitest --run --config vite.config.ts test/vitest/integration/build-id-prefix.integration.test.ts",
|
|
22
|
+
"test:integration:cache-components": "vitest --run --config vitest.cache-components.config.ts",
|
|
23
|
+
"test:e2e": "playwright test",
|
|
24
|
+
"run-dev-server": "pnpm run-dev-server-15-4-11",
|
|
25
|
+
"run-dev-server-15-4-11": "cd ./test/nextjs-test-projects/next-app-15-4-11 && pnpm dev"
|
|
28
26
|
},
|
|
29
27
|
"main": "dist/index.js",
|
|
30
28
|
"module": "dist/index.mjs",
|
|
@@ -56,9 +54,9 @@
|
|
|
56
54
|
"tags-cache",
|
|
57
55
|
"nextjs-turbo-redis-cache"
|
|
58
56
|
],
|
|
59
|
-
"author": "
|
|
57
|
+
"author": "TRWK <info@trwk.de>",
|
|
60
58
|
"license": "ISC",
|
|
61
|
-
"description": "
|
|
59
|
+
"description": "Designed for speed, scalability, and optimized performance, nextjs-turbo-redis-cache is your custom cache handler for demanding production environments.",
|
|
62
60
|
"publishConfig": {
|
|
63
61
|
"access": "public",
|
|
64
62
|
"provenance": true
|
|
@@ -78,7 +76,7 @@
|
|
|
78
76
|
"devDependencies": {
|
|
79
77
|
"@commitlint/cli": "^19.6.0",
|
|
80
78
|
"@commitlint/config-conventional": "^19.6.0",
|
|
81
|
-
"@playwright/test": "^1.
|
|
79
|
+
"@playwright/test": "^1.62.0",
|
|
82
80
|
"@semantic-release/changelog": "^6.0.3",
|
|
83
81
|
"@semantic-release/git": "^10.0.1",
|
|
84
82
|
"@semantic-release/github": "^12.0.2",
|
package/playwright.config.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { defineConfig } from '@playwright/test';
|
|
2
2
|
|
|
3
|
+
const testApp =
|
|
4
|
+
process.env.PLAYWRIGHT_TEST_APP || 'next-app-16-2-6-cache-components';
|
|
5
|
+
|
|
3
6
|
export default defineConfig({
|
|
4
|
-
testDir: '
|
|
7
|
+
testDir: 'test/playwright',
|
|
5
8
|
use: {
|
|
6
9
|
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3101',
|
|
7
10
|
trace: 'on-first-retry',
|
|
8
11
|
},
|
|
9
12
|
webServer: {
|
|
10
|
-
command:
|
|
11
|
-
'pnpm -C test/integration/next-app-16-2-3-cache-components dev -p 3101',
|
|
13
|
+
command: `pnpm -C test/nextjs-test-projects/${testApp} dev -p 3101`,
|
|
12
14
|
url: 'http://localhost:3101',
|
|
13
15
|
reuseExistingServer: true,
|
|
14
16
|
timeout: 120_000,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
redisErrorHandler,
|
|
7
7
|
} from './RedisStringsHandler';
|
|
8
8
|
import { SyncedMap } from './SyncedMap';
|
|
9
|
+
import { DeduplicatedRequestHandler } from './DeduplicatedRequestHandler';
|
|
9
10
|
import { debug } from './utils/debug';
|
|
10
11
|
import { resolveKeyPrefix } from './utils/prefix';
|
|
11
12
|
|
|
@@ -87,8 +88,19 @@ class RedisCacheComponentsHandler implements CacheComponentsHandler {
|
|
|
87
88
|
private client: Client;
|
|
88
89
|
private revalidatedTagsMap: SyncedMap<number>;
|
|
89
90
|
private sharedTagsMap: SyncedMap<string[]>;
|
|
91
|
+
private inMemoryDeduplicationCache: SyncedMap<
|
|
92
|
+
Promise<ReturnType<Client['get']>>
|
|
93
|
+
>;
|
|
90
94
|
private keyPrefix: string;
|
|
91
95
|
private getTimeoutMs: number;
|
|
96
|
+
private redisGet: Client['get'];
|
|
97
|
+
private redisDeduplicationHandler: DeduplicatedRequestHandler<
|
|
98
|
+
Client['get'],
|
|
99
|
+
string | Buffer | null
|
|
100
|
+
>;
|
|
101
|
+
private deduplicatedRedisGet: (key: string) => Client['get'];
|
|
102
|
+
private redisGetDeduplication: boolean;
|
|
103
|
+
private inMemoryCachingTime: number;
|
|
92
104
|
|
|
93
105
|
constructor({
|
|
94
106
|
redisUrl = process.env.REDIS_URL
|
|
@@ -103,6 +115,8 @@ class RedisCacheComponentsHandler implements CacheComponentsHandler {
|
|
|
103
115
|
: 500,
|
|
104
116
|
revalidateTagQuerySize = 250,
|
|
105
117
|
avgResyncIntervalMs = 60 * 60 * 1_000,
|
|
118
|
+
redisGetDeduplication = true,
|
|
119
|
+
inMemoryCachingTime = 10_000,
|
|
106
120
|
socketOptions,
|
|
107
121
|
clientOptions,
|
|
108
122
|
killContainerOnErrorThreshold = process.env
|
|
@@ -118,6 +132,8 @@ class RedisCacheComponentsHandler implements CacheComponentsHandler {
|
|
|
118
132
|
env: process.env,
|
|
119
133
|
});
|
|
120
134
|
this.getTimeoutMs = getTimeoutMs;
|
|
135
|
+
this.redisGetDeduplication = redisGetDeduplication;
|
|
136
|
+
this.inMemoryCachingTime = inMemoryCachingTime;
|
|
121
137
|
|
|
122
138
|
this.client = createClient({
|
|
123
139
|
url: redisUrl,
|
|
@@ -208,6 +224,29 @@ class RedisCacheComponentsHandler implements CacheComponentsHandler {
|
|
|
208
224
|
avgResyncIntervalMs / 10 +
|
|
209
225
|
Math.random() * (avgResyncIntervalMs / 10),
|
|
210
226
|
});
|
|
227
|
+
|
|
228
|
+
this.inMemoryDeduplicationCache = new SyncedMap({
|
|
229
|
+
client: this.client,
|
|
230
|
+
keyPrefix: this.keyPrefix,
|
|
231
|
+
redisKey: '__cacheComponents_inMemoryDeduplicationCache__',
|
|
232
|
+
database,
|
|
233
|
+
querySize: revalidateTagQuerySize,
|
|
234
|
+
filterKeys,
|
|
235
|
+
customizedSync: {
|
|
236
|
+
withoutRedisHashmap: true,
|
|
237
|
+
withoutSetSync: true,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const redisGet: Client['get'] = this.client.get.bind(this.client);
|
|
242
|
+
this.redisDeduplicationHandler = new DeduplicatedRequestHandler(
|
|
243
|
+
redisGet,
|
|
244
|
+
inMemoryCachingTime,
|
|
245
|
+
this.inMemoryDeduplicationCache,
|
|
246
|
+
);
|
|
247
|
+
this.redisGet = redisGet;
|
|
248
|
+
this.deduplicatedRedisGet =
|
|
249
|
+
this.redisDeduplicationHandler.deduplicatedFunction;
|
|
211
250
|
} catch (error) {
|
|
212
251
|
console.error('RedisCacheComponentsHandler constructor error', error);
|
|
213
252
|
throw error;
|
|
@@ -253,12 +292,18 @@ class RedisCacheComponentsHandler implements CacheComponentsHandler {
|
|
|
253
292
|
try {
|
|
254
293
|
await this.assertClientIsReady();
|
|
255
294
|
|
|
295
|
+
const clientGet = this.redisGetDeduplication
|
|
296
|
+
? this.deduplicatedRedisGet(cacheKey)
|
|
297
|
+
: this.redisGet;
|
|
298
|
+
|
|
256
299
|
const serialized = await redisErrorHandler(
|
|
257
|
-
'RedisCacheComponentsHandler.get(), operation: get
|
|
300
|
+
'RedisCacheComponentsHandler.get(), operation: get' +
|
|
301
|
+
(this.redisGetDeduplication ? ' deduplicated' : '') +
|
|
302
|
+
' ' +
|
|
258
303
|
this.getTimeoutMs +
|
|
259
304
|
'ms ' +
|
|
260
305
|
redisKey,
|
|
261
|
-
|
|
306
|
+
clientGet(
|
|
262
307
|
commandOptions({ signal: AbortSignal.timeout(this.getTimeoutMs) }),
|
|
263
308
|
redisKey,
|
|
264
309
|
),
|
|
@@ -355,6 +400,10 @@ class RedisCacheComponentsHandler implements CacheComponentsHandler {
|
|
|
355
400
|
throw jsonError;
|
|
356
401
|
}
|
|
357
402
|
|
|
403
|
+
if (this.redisGetDeduplication) {
|
|
404
|
+
this.redisDeduplicationHandler.seedRequestReturn(cacheKey, serialized);
|
|
405
|
+
}
|
|
406
|
+
|
|
358
407
|
// expire is already a duration in seconds, use it directly
|
|
359
408
|
const ttlSeconds =
|
|
360
409
|
Number.isFinite(stored.expire) && stored.expire > 0
|
|
@@ -450,6 +499,12 @@ class RedisCacheComponentsHandler implements CacheComponentsHandler {
|
|
|
450
499
|
this.client.unlink(fullRedisKeys),
|
|
451
500
|
);
|
|
452
501
|
|
|
502
|
+
if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {
|
|
503
|
+
for (const key of keysToDelete) {
|
|
504
|
+
this.inMemoryDeduplicationCache.delete(key);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
453
508
|
// Delete from sharedTagsMap
|
|
454
509
|
const deleteTagsOperation = this.sharedTagsMap.delete(cacheKeys);
|
|
455
510
|
await deleteTagsOperation;
|
|
@@ -2,7 +2,7 @@ import { commandOptions, createClient, RedisClientOptions } from 'redis';
|
|
|
2
2
|
import { SyncedMap } from './SyncedMap';
|
|
3
3
|
import { DeduplicatedRequestHandler } from './DeduplicatedRequestHandler';
|
|
4
4
|
import { debug } from './utils/debug';
|
|
5
|
-
import {
|
|
5
|
+
import { CacheValueSerializer, jsonCacheValueSerializer } from './serializer';
|
|
6
6
|
|
|
7
7
|
export type CommandOptions = ReturnType<typeof commandOptions>;
|
|
8
8
|
export type Client = ReturnType<typeof createClient>;
|
|
@@ -113,6 +113,27 @@ export type CreateRedisStringsHandlerOptions = {
|
|
|
113
113
|
* @example { username: 'user', password: 'pass' }
|
|
114
114
|
*/
|
|
115
115
|
clientOptions?: Omit<RedisClientOptions, 'url' | 'database' | 'socket'>;
|
|
116
|
+
/** Pluggable wire-format codec for Redis string values. Lets you plug in
|
|
117
|
+
* compression (gzip/brotli), encryption, or any other custom encoding without
|
|
118
|
+
* forking this package or losing the existing dedup / batch / keyspace features.
|
|
119
|
+
*
|
|
120
|
+
* Both `serialize` and `deserialize` may return a `Promise`, enabling
|
|
121
|
+
* non-blocking async codecs (e.g. `zlib.brotliCompress`) that don't block the
|
|
122
|
+
* Node.js event loop. Synchronous implementations continue to work unchanged.
|
|
123
|
+
*
|
|
124
|
+
* Only the main cache-entry storage path is routed through the serializer.
|
|
125
|
+
* The shared-tags map and the revalidated-tags map are not. The in-memory
|
|
126
|
+
* deduplication cache stores the wire-format string verbatim - its contents
|
|
127
|
+
* change with the serializer, but the cache itself is not re-encoded.
|
|
128
|
+
*
|
|
129
|
+
* Operational note: changing the serializer (or any of its parameters such as
|
|
130
|
+
* a compression level or encryption key) makes existing Redis keys unreadable.
|
|
131
|
+
* Either flush the affected keys or bump `keyPrefix` before deploying.
|
|
132
|
+
*
|
|
133
|
+
* @default jsonCacheValueSerializer (JSON.stringify with bufferAndMapReplacer
|
|
134
|
+
* so native Buffer and Map values inside a CacheEntry round-trip transparently)
|
|
135
|
+
*/
|
|
136
|
+
valueSerializer?: CacheValueSerializer;
|
|
116
137
|
};
|
|
117
138
|
|
|
118
139
|
// Identifier prefix used by Next.js to mark automatically generated cache tags
|
|
@@ -144,6 +165,7 @@ export default class RedisStringsHandler {
|
|
|
144
165
|
private defaultStaleAge: number;
|
|
145
166
|
private estimateExpireAge: (staleAge: number) => number;
|
|
146
167
|
private killContainerOnErrorThreshold: number;
|
|
168
|
+
private valueSerializer: CacheValueSerializer;
|
|
147
169
|
|
|
148
170
|
constructor({
|
|
149
171
|
redisUrl = process.env.REDIS_URL
|
|
@@ -172,6 +194,7 @@ export default class RedisStringsHandler {
|
|
|
172
194
|
: 0,
|
|
173
195
|
socketOptions,
|
|
174
196
|
clientOptions,
|
|
197
|
+
valueSerializer = jsonCacheValueSerializer,
|
|
175
198
|
}: CreateRedisStringsHandlerOptions) {
|
|
176
199
|
try {
|
|
177
200
|
this.keyPrefix = keyPrefix;
|
|
@@ -181,6 +204,7 @@ export default class RedisStringsHandler {
|
|
|
181
204
|
this.estimateExpireAge = estimateExpireAge;
|
|
182
205
|
this.killContainerOnErrorThreshold = killContainerOnErrorThreshold;
|
|
183
206
|
this.getTimeoutMs = getTimeoutMs;
|
|
207
|
+
this.valueSerializer = valueSerializer;
|
|
184
208
|
|
|
185
209
|
try {
|
|
186
210
|
// Create Redis client with properly typed configuration
|
|
@@ -418,10 +442,24 @@ export default class RedisStringsHandler {
|
|
|
418
442
|
return null;
|
|
419
443
|
}
|
|
420
444
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
445
|
+
let cacheEntry: CacheEntry | null;
|
|
446
|
+
try {
|
|
447
|
+
cacheEntry =
|
|
448
|
+
await this.valueSerializer.deserialize(serializedCacheEntry);
|
|
449
|
+
} catch (err) {
|
|
450
|
+
// A decode failure (e.g. legacy/corrupted entry after swapping codecs,
|
|
451
|
+
// bumping a compression level or rotating an encryption key) is treated
|
|
452
|
+
// as a cache miss rather than a hard error - this matches the
|
|
453
|
+
// null-from-deserialize semantics in the CacheValueSerializer contract
|
|
454
|
+
// and prevents a single unreadable entry from incrementing
|
|
455
|
+
// killContainerOnErrorCount on every read.
|
|
456
|
+
console.warn(
|
|
457
|
+
'RedisStringsHandler.get() valueSerializer.deserialize failed, treating as cache miss',
|
|
458
|
+
this.keyPrefix + key,
|
|
459
|
+
err,
|
|
460
|
+
);
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
425
463
|
|
|
426
464
|
debug(
|
|
427
465
|
'green',
|
|
@@ -520,8 +558,9 @@ export default class RedisStringsHandler {
|
|
|
520
558
|
} catch (error) {
|
|
521
559
|
// This catch block is necessary to handle any errors that may occur during:
|
|
522
560
|
// 1. Redis operations (get, unlink)
|
|
523
|
-
// 2.
|
|
524
|
-
//
|
|
561
|
+
// 2. Tag validation and cleanup
|
|
562
|
+
// (Deserialization errors are handled locally above and treated as cache misses,
|
|
563
|
+
// so they do not reach this branch and do not count toward the kill threshold.)
|
|
525
564
|
// If any error occurs, we return null to indicate no valid cache entry was found,
|
|
526
565
|
// allowing the application to regenerate the content rather than crash
|
|
527
566
|
console.error(
|
|
@@ -621,10 +660,8 @@ export default class RedisStringsHandler {
|
|
|
621
660
|
tags: ctx?.tags || [],
|
|
622
661
|
value: data,
|
|
623
662
|
};
|
|
624
|
-
const serializedCacheEntry =
|
|
625
|
-
cacheEntry
|
|
626
|
-
bufferAndMapReplacer,
|
|
627
|
-
);
|
|
663
|
+
const serializedCacheEntry =
|
|
664
|
+
await this.valueSerializer.serialize(cacheEntry);
|
|
628
665
|
|
|
629
666
|
// pre seed data into deduplicated get client. This will reduce redis load by not requesting
|
|
630
667
|
// the same value from redis which was just set.
|