From 3938d85a7fd2111035c2a2741d8fbaa81d4460e2 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Tue, 14 Oct 2025 16:54:23 -0700 Subject: [PATCH] [Cache Components] Remove `unstable` prefix from `unstable_cacheTag` `cacheLife` is now stable and does not require an unstable prefix --- .../src/transforms/react_server_components.rs | 2 +- .../04-functions/cacheTag.mdx | 12 +++---- errors/next-prerender-missing-suspense.mdx | 2 +- packages/next/cache.d.ts | 2 +- packages/next/cache.js | 4 +-- .../plugins/next-types-plugin/index.ts | 2 +- .../next-cache-in-client/cachetag/page.js | 8 +++++ .../unstable_cachetag/page.js | 8 ----- .../acceptance-app/rsc-build-errors.test.ts | 32 +++++++++---------- ...component-compiler-errors-in-pages.test.ts | 2 +- .../app/cached/page.tsx | 4 +-- .../revalidation/app/greeting/page.tsx | 2 +- .../use-cache-custom-handler/app/page.tsx | 7 +--- .../revalidate-and-redirect/page.tsx | 2 +- .../app/(dynamic)/revalidate-and-use/page.tsx | 4 +-- .../app/(partially-static)/api/route.ts | 4 +-- .../app/(partially-static)/cache-tag/page.tsx | 2 +- .../app/(partially-static)/form/page.tsx | 2 +- .../app-dir/resume-data-cache/app/page.tsx | 4 +-- test/rspack-dev-tests-manifest.json | 4 +-- test/turbopack-dev-tests-manifest.json | 4 +-- 21 files changed, 53 insertions(+), 60 deletions(-) create mode 100644 test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/next-cache-in-client/cachetag/page.js delete mode 100644 test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/next-cache-in-client/unstable_cachetag/page.js diff --git a/crates/next-custom-transforms/src/transforms/react_server_components.rs b/crates/next-custom-transforms/src/transforms/react_server_components.rs index 62253693e4e6b..05735ec12304d 100644 --- a/crates/next-custom-transforms/src/transforms/react_server_components.rs +++ b/crates/next-custom-transforms/src/transforms/react_server_components.rs @@ -653,7 +653,7 @@ impl ReactServerComponentValidator { "revalidateTag", // "unstable_cache", // useless in client, but doesn't technically error "cacheLife", - "unstable_cacheTag", + "cacheTag", // "unstable_noStore" // no-op in client, but allowed for legacy reasons ], ), diff --git a/docs/01-app/03-api-reference/04-functions/cacheTag.mdx b/docs/01-app/03-api-reference/04-functions/cacheTag.mdx index 5fa4e3c2d67e2..54bc2312d2b05 100644 --- a/docs/01-app/03-api-reference/04-functions/cacheTag.mdx +++ b/docs/01-app/03-api-reference/04-functions/cacheTag.mdx @@ -43,7 +43,7 @@ export default nextConfig The `cacheTag` function takes one or more string values. ```tsx filename="app/data.ts" switcher -import { unstable_cacheTag as cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' export async function getData() { 'use cache' @@ -54,7 +54,7 @@ export async function getData() { ``` ```jsx filename="app/data.js" switcher -import { unstable_cacheTag as cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' export async function getData() { 'use cache' @@ -104,7 +104,7 @@ cacheTag('tag-one', 'tag-two') Tag your cached data by calling `cacheTag` within a cached function or component: ```tsx filename="app/components/bookings.tsx" switcher -import { unstable_cacheTag as cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' interface BookingsProps { type: string @@ -124,7 +124,7 @@ export async function Bookings({ type = 'haircut' }: BookingsProps) { ``` ```jsx filename="app/components/bookings.js" switcher -import { unstable_cacheTag as cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' export async function Bookings({ type = 'haircut' }) { 'use cache' @@ -144,7 +144,7 @@ export async function Bookings({ type = 'haircut' }) { You can use the data returned from an async function to tag the cache entry. ```tsx filename="app/components/bookings.tsx" switcher -import { unstable_cacheTag as cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' interface BookingsProps { type: string @@ -162,7 +162,7 @@ export async function Bookings({ type = 'haircut' }: BookingsProps) { ``` ```jsx filename="app/components/bookings.js" switcher -import { unstable_cacheTag as cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' export async function Bookings({ type = 'haircut' }) { async function getBookingsData() { diff --git a/errors/next-prerender-missing-suspense.mdx b/errors/next-prerender-missing-suspense.mdx index 637241fc1891f..21fdadf16420f 100644 --- a/errors/next-prerender-missing-suspense.mdx +++ b/errors/next-prerender-missing-suspense.mdx @@ -34,7 +34,7 @@ export default async function Page() { After: ```jsx filename="app/page.js" -import { unstable_cacheTag as cacheTag, cacheLife } from 'next/cache' +import { cacheTag, cacheLife } from 'next/cache' async function getRecentArticles() { "use cache" diff --git a/packages/next/cache.d.ts b/packages/next/cache.d.ts index 2fc5dc32509cc..f84d7c26487ba 100644 --- a/packages/next/cache.d.ts +++ b/packages/next/cache.d.ts @@ -9,7 +9,7 @@ export { export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store' -export { cacheTag as unstable_cacheTag } from 'next/dist/server/use-cache/cache-tag' +export { cacheTag } from 'next/dist/server/use-cache/cache-tag' /** * Cache this `"use cache"` for a timespan defined by the `"default"` profile. diff --git a/packages/next/cache.js b/packages/next/cache.js index 2dfebc271790f..b035b0c190e93 100644 --- a/packages/next/cache.js +++ b/packages/next/cache.js @@ -16,7 +16,7 @@ const cacheExports = { require('next/dist/server/web/spec-extension/unstable-no-store') .unstable_noStore, cacheLife: require('next/dist/server/use-cache/cache-life').cacheLife, - unstable_cacheTag: require('next/dist/server/use-cache/cache-tag').cacheTag, + cacheTag: require('next/dist/server/use-cache/cache-tag').cacheTag, } // https://nodejs.org/api/esm.html#commonjs-namespaces @@ -30,5 +30,5 @@ exports.revalidateTag = cacheExports.revalidateTag exports.updateTag = cacheExports.updateTag exports.unstable_noStore = cacheExports.unstable_noStore exports.cacheLife = cacheExports.cacheLife -exports.unstable_cacheTag = cacheExports.unstable_cacheTag +exports.cacheTag = cacheExports.cacheTag exports.refresh = cacheExports.refresh diff --git a/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts b/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts index 1180f220722da..dbafb9a7975a6 100644 --- a/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts +++ b/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts @@ -538,7 +538,7 @@ declare module 'next/cache' { ${overloads} - export { cacheTag as unstable_cacheTag } from 'next/dist/server/use-cache/cache-tag' + export { cacheTag } from 'next/dist/server/use-cache/cache-tag' } ` } diff --git a/test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/next-cache-in-client/cachetag/page.js b/test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/next-cache-in-client/cachetag/page.js new file mode 100644 index 0000000000000..ef412cfd07769 --- /dev/null +++ b/test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/next-cache-in-client/cachetag/page.js @@ -0,0 +1,8 @@ +'use client' +import { cacheTag } from 'next/cache' + +console.log({ cacheTag }) + +export default function Page() { + return null +} diff --git a/test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/next-cache-in-client/unstable_cachetag/page.js b/test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/next-cache-in-client/unstable_cachetag/page.js deleted file mode 100644 index a75f2e7b61286..0000000000000 --- a/test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/next-cache-in-client/unstable_cachetag/page.js +++ /dev/null @@ -1,8 +0,0 @@ -'use client' -import { unstable_cacheTag } from 'next/cache' - -console.log({ unstable_cacheTag }) - -export default function Page() { - return null -} diff --git a/test/development/acceptance-app/rsc-build-errors.test.ts b/test/development/acceptance-app/rsc-build-errors.test.ts index 8105836e6f06e..1ca5b858c5fef 100644 --- a/test/development/acceptance-app/rsc-build-errors.test.ts +++ b/test/development/acceptance-app/rsc-build-errors.test.ts @@ -279,23 +279,21 @@ describe('Error overlay - RSC build errors', () => { }) describe("importing 'next/cache' APIs in a client component", () => { - test.each([ - 'revalidatePath', - 'revalidateTag', - 'cacheLife', - 'unstable_cacheTag', - ])('%s is not allowed', async (api) => { - await using sandbox = await createSandbox( - next, - undefined, - `/server-with-errors/next-cache-in-client/${api.toLowerCase()}` - ) - const { session } = sandbox - await session.assertHasRedbox() - expect(await session.getRedboxSource()).toInclude( - `You're importing a component that needs "${api}". That only works in a Server Component but one of its parents is marked with "use client", so it's a Client Component.` - ) - }) + test.each(['revalidatePath', 'revalidateTag', 'cacheLife', 'cacheTag'])( + '%s is not allowed', + async (api) => { + await using sandbox = await createSandbox( + next, + undefined, + `/server-with-errors/next-cache-in-client/${api.toLowerCase()}` + ) + const { session } = sandbox + await session.assertHasRedbox() + expect(await session.getRedboxSource()).toInclude( + `You're importing a component that needs "${api}". That only works in a Server Component but one of its parents is marked with "use client", so it's a Client Component.` + ) + } + ) test.each([ 'unstable_cache', // useless in client, but doesn't technically error diff --git a/test/development/acceptance/server-component-compiler-errors-in-pages.test.ts b/test/development/acceptance/server-component-compiler-errors-in-pages.test.ts index 624002ff44dea..c23cd32cbfc56 100644 --- a/test/development/acceptance/server-component-compiler-errors-in-pages.test.ts +++ b/test/development/acceptance/server-component-compiler-errors-in-pages.test.ts @@ -385,7 +385,7 @@ describe('Error Overlay for server components compiler errors in pages', () => { 'revalidatePath', 'revalidateTag', 'cacheLife', - 'unstable_cacheTag', + 'cacheTag', 'revalidatePath', 'revalidateTag', ])('%s is not allowed', async (api) => { diff --git a/test/development/app-dir/cache-components-dev-cache-scope/app/cached/page.tsx b/test/development/app-dir/cache-components-dev-cache-scope/app/cached/page.tsx index 65e7d43f84c41..74f5d486ce02c 100644 --- a/test/development/app-dir/cache-components-dev-cache-scope/app/cached/page.tsx +++ b/test/development/app-dir/cache-components-dev-cache-scope/app/cached/page.tsx @@ -1,4 +1,4 @@ -import { revalidateTag, cacheLife, unstable_cacheTag } from 'next/cache' +import { revalidateTag, cacheLife, cacheTag } from 'next/cache' import { fetchData } from '../api/data' // import { Suspense } from 'react' // import { cookies, headers } from 'next/headers' @@ -19,7 +19,7 @@ async function reload() { async function Component() { 'use cache' cacheLife({ revalidate: 30 }) - unstable_cacheTag('hello') + cacheTag('hello') return {await fetchData()} } diff --git a/test/e2e/app-dir/segment-cache/revalidation/app/greeting/page.tsx b/test/e2e/app-dir/segment-cache/revalidation/app/greeting/page.tsx index 8d88d91575b03..334fc4c093537 100644 --- a/test/e2e/app-dir/segment-cache/revalidation/app/greeting/page.tsx +++ b/test/e2e/app-dir/segment-cache/revalidation/app/greeting/page.tsx @@ -1,6 +1,6 @@ 'use cache' -import { unstable_cacheTag as cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' import { Suspense } from 'react' const TEST_DATA_SERVICE_URL = process.env.TEST_DATA_SERVICE_URL diff --git a/test/e2e/app-dir/use-cache-custom-handler/app/page.tsx b/test/e2e/app-dir/use-cache-custom-handler/app/page.tsx index d4c10c565db86..40909ff85113c 100644 --- a/test/e2e/app-dir/use-cache-custom-handler/app/page.tsx +++ b/test/e2e/app-dir/use-cache-custom-handler/app/page.tsx @@ -1,10 +1,5 @@ import { Suspense } from 'react' -import { - cacheLife, - unstable_cacheTag as cacheTag, - revalidatePath, - updateTag, -} from 'next/cache' +import { cacheLife, cacheTag, revalidatePath, updateTag } from 'next/cache' import { redirect } from 'next/navigation' import { connection } from 'next/server' import React from 'react' diff --git a/test/e2e/app-dir/use-cache/app/(dynamic)/revalidate-and-redirect/page.tsx b/test/e2e/app-dir/use-cache/app/(dynamic)/revalidate-and-redirect/page.tsx index 2e19caba05eb5..8189c905928f9 100644 --- a/test/e2e/app-dir/use-cache/app/(dynamic)/revalidate-and-redirect/page.tsx +++ b/test/e2e/app-dir/use-cache/app/(dynamic)/revalidate-and-redirect/page.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { unstable_cacheTag as cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' import Link from 'next/link' import { connection } from 'next/server' diff --git a/test/e2e/app-dir/use-cache/app/(dynamic)/revalidate-and-use/page.tsx b/test/e2e/app-dir/use-cache/app/(dynamic)/revalidate-and-use/page.tsx index 871f463d0d870..f7be5597415bd 100644 --- a/test/e2e/app-dir/use-cache/app/(dynamic)/revalidate-and-use/page.tsx +++ b/test/e2e/app-dir/use-cache/app/(dynamic)/revalidate-and-use/page.tsx @@ -1,4 +1,4 @@ -import { revalidatePath, unstable_cacheTag, updateTag } from 'next/cache' +import { revalidatePath, cacheTag, updateTag } from 'next/cache' import { Form } from './form' import { connection } from 'next/server' @@ -10,7 +10,7 @@ async function fetchCachedValue() { async function getCachedValue() { 'use cache' - unstable_cacheTag('revalidate-and-use') + cacheTag('revalidate-and-use') return Math.random() } diff --git a/test/e2e/app-dir/use-cache/app/(partially-static)/api/route.ts b/test/e2e/app-dir/use-cache/app/(partially-static)/api/route.ts index c24d01ee2c06c..1ce2c68f86920 100644 --- a/test/e2e/app-dir/use-cache/app/(partially-static)/api/route.ts +++ b/test/e2e/app-dir/use-cache/app/(partially-static)/api/route.ts @@ -1,8 +1,8 @@ -import { unstable_cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' async function getCachedRandom() { 'use cache' - unstable_cacheTag('api') + cacheTag('api') return Math.random() } diff --git a/test/e2e/app-dir/use-cache/app/(partially-static)/cache-tag/page.tsx b/test/e2e/app-dir/use-cache/app/(partially-static)/cache-tag/page.tsx index ad50c97db7db4..c0b4a26728baf 100644 --- a/test/e2e/app-dir/use-cache/app/(partially-static)/cache-tag/page.tsx +++ b/test/e2e/app-dir/use-cache/app/(partially-static)/cache-tag/page.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { unstable_cacheTag as cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' import { RevalidateButtons } from './buttons' async function getCachedWithTag({ diff --git a/test/e2e/app-dir/use-cache/app/(partially-static)/form/page.tsx b/test/e2e/app-dir/use-cache/app/(partially-static)/form/page.tsx index 504805f15dd08..c0f70bd2f013e 100644 --- a/test/e2e/app-dir/use-cache/app/(partially-static)/form/page.tsx +++ b/test/e2e/app-dir/use-cache/app/(partially-static)/form/page.tsx @@ -1,4 +1,4 @@ -import { updateTag, unstable_cacheTag as cacheTag } from 'next/cache' +import { updateTag, cacheTag } from 'next/cache' async function refresh() { 'use server' diff --git a/test/production/app-dir/resume-data-cache/app/page.tsx b/test/production/app-dir/resume-data-cache/app/page.tsx index a42bd6f524a39..efe4d038cb1d7 100644 --- a/test/production/app-dir/resume-data-cache/app/page.tsx +++ b/test/production/app-dir/resume-data-cache/app/page.tsx @@ -1,11 +1,11 @@ import React, { Suspense } from 'react' import { connection } from 'next/server' -import { unstable_cacheTag } from 'next/cache' +import { cacheTag } from 'next/cache' async function getRandomNumber() { 'use cache' - unstable_cacheTag('test') + cacheTag('test') return Math.random() } diff --git a/test/rspack-dev-tests-manifest.json b/test/rspack-dev-tests-manifest.json index 774caa0657b10..2b6f711db1c47 100644 --- a/test/rspack-dev-tests-manifest.json +++ b/test/rspack-dev-tests-manifest.json @@ -220,7 +220,7 @@ "Error overlay - RSC build errors importing 'next/cache' APIs in a client component revalidateTag is not allowed", "Error overlay - RSC build errors importing 'next/cache' APIs in a client component unstable_cache is allowed", "Error overlay - RSC build errors importing 'next/cache' APIs in a client component cacheLife is not allowed", - "Error overlay - RSC build errors importing 'next/cache' APIs in a client component unstable_cacheTag is not allowed", + "Error overlay - RSC build errors importing 'next/cache' APIs in a client component cacheTag is not allowed", "Error overlay - RSC build errors importing 'next/cache' APIs in a client component unstable_noStore is allowed", "Error overlay - RSC build errors next/root-params importing 'next/root-params' in a client component", "Error overlay - RSC build errors next/root-params importing 'next/root-params' in a client component in a way that bypasses import analysis", @@ -464,7 +464,7 @@ "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages revalidateTag is not allowed", "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages unstable_cache is allowed", "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages cacheLife is not allowed", - "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages unstable_cacheTag is not allowed", + "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages cacheTag is not allowed", "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages unstable_noStore is allowed", "Error Overlay for server components compiler errors in pages importing 'next/headers' in pages", "Error Overlay for server components compiler errors in pages importing 'next/root-params' in pages", diff --git a/test/turbopack-dev-tests-manifest.json b/test/turbopack-dev-tests-manifest.json index 1be6dea72a644..5f3ebdbc9032b 100644 --- a/test/turbopack-dev-tests-manifest.json +++ b/test/turbopack-dev-tests-manifest.json @@ -2019,7 +2019,7 @@ "Error overlay - RSC build errors importing 'next/cache' APIs in a client component revalidateTag is not allowed", "Error overlay - RSC build errors importing 'next/cache' APIs in a client component unstable_cache is allowed", "Error overlay - RSC build errors importing 'next/cache' APIs in a client component cacheLife is not allowed", - "Error overlay - RSC build errors importing 'next/cache' APIs in a client component unstable_cacheTag is not allowed", + "Error overlay - RSC build errors importing 'next/cache' APIs in a client component cacheTag is not allowed", "Error overlay - RSC build errors importing 'next/cache' APIs in a client component revalidatePath is not allowed", "Error overlay - RSC build errors importing 'next/cache' APIs in a client component revalidateTag is not allowed", "Error overlay - RSC build errors importing 'next/cache' APIs in a client component unstable_noStore is allowed", @@ -2274,7 +2274,7 @@ "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages revalidateTag is not allowed", "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages unstable_cache is allowed", "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages cacheLife is not allowed", - "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages unstable_cacheTag is not allowed", + "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages cacheTag is not allowed", "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages revalidatePath is not allowed", "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages revalidateTag is not allowed", "Error Overlay for server components compiler errors in pages importing 'next/cache' APIs in pages unstable_noStore is allowed",