Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/next-api/src/server_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,10 +634,10 @@ async fn module_hash(
} else {
None
};
let code = chunk_item.code(async_info);
let factory = chunk_item.code(async_info).await?;
RcStr::from(deterministic_hash(
"",
(ident_str, code.source_code_hash().await?),
(ident_str, factory.code.to_code().source_code_hash().await?),
HashAlgorithm::Xxh3Hash128Hex,
))
} else {
Expand Down
18 changes: 13 additions & 5 deletions docs/01-app/02-guides/upgrading/codemods.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,13 @@ npx @next/codemod upgrade canary --yes
npx @next/codemod@canary cache-components-instant-false ./app
```

This codemod adds `export const instant = false` to every `{page,layout,default}` file in your app directory that doesn't already export `instant`, so you can enable [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) and then remove the opt-outs route by route. It skips Client Components (`"use client"`) and files that already declare `instant`.
This codemod adds `export const instant = false` to every `{page,layout,default}` file in your app directory that doesn't already export `instant`, so you can enable [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) and then remove the opt-outs route by route. It skips Client Components (`"use client"`) and files that already declare `instant`. The generated `@next-codemod-ignore` comment marks the intentional opt-out without blocking compilation.

> **Good to know**: Pass `./src/app` in a `src/` project. A wrong path reports `0 ok` instead of failing, so check the file count.

```diff filename="app/page.tsx"
+ // TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
+ // @next-codemod-ignore Cache Components adoption: this segment temporarily allows blocking.
+ // Remove this opt-out after verifying the segment passes validation without it.
+ // See: https://nextjs.org/docs/app/guides/migrating-to-cache-components
+ export const instant = false
+
Expand Down Expand Up @@ -291,7 +292,9 @@ npx @next/codemod@latest next-async-request-api .
```

This codemod will transform dynamic APIs (`cookies()`, `headers()` and `draftMode()` from `next/headers`) that are now asynchronous to be properly awaited or wrapped with `React.use()` if applicable.
When an automatic migration isn't possible, the codemod will either add a typecast (if a TypeScript file) or a comment to inform the user that it needs to be manually reviewed & updated.
When automatic migration isn't possible, the codemod adds an `@next-codemod-error` comment and may add a temporary `UnsafeUnwrapped*` cast. Complete the migration before removing them. If the suggested change doesn't apply, replace the directive with `@next-codemod-ignore` and explain why.

See the [Async Request APIs migration guide](/docs/app/guides/upgrading/version-15#async-request-apis-breaking-change) for examples.

For example:

Expand Down Expand Up @@ -323,7 +326,9 @@ import {
type UnsafeUnwrappedCookies,
type UnsafeUnwrappedHeaders,
} from 'next/headers'
const token = (cookies() as unknown as UnsafeUnwrappedCookies).get('token')
const token =
/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedCookies cast after repairing the migration. */
(cookies() as unknown as UnsafeUnwrappedCookies).get('token')

function useToken() {
const token = use(cookies()).get('token')
Expand All @@ -335,7 +340,10 @@ export default async function Page() {
}

function getHeader() {
return (headers() as unknown as UnsafeUnwrappedHeaders).get('x-foo')
return (
/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedHeaders cast after repairing the migration. */
(headers() as unknown as UnsafeUnwrappedHeaders).get('x-foo')
)
}
```

Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"registry": "https://registry.npmjs.org/"
}
},
"version": "16.4.0-canary.28"
"version": "16.4.0-canary.29"
}
2 changes: 1 addition & 1 deletion packages/create-next-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
"version": "16.4.0-canary.28",
"version": "16.4.0-canary.29",
"keywords": [
"react",
"next",
Expand Down
2 changes: 1 addition & 1 deletion packages/devlow-bench/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vercel/devlow-bench",
"version": "16.4.0-canary.28",
"version": "16.4.0-canary.29",
"description": "Benchmarking tool for the developer workflow",
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-config-next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
"version": "16.4.0-canary.28",
"version": "16.4.0-canary.29",
"description": "ESLint configuration used by Next.js.",
"license": "MIT",
"repository": {
Expand All @@ -12,7 +12,7 @@
"dist"
],
"dependencies": {
"@next/eslint-plugin-next": "16.4.0-canary.28",
"@next/eslint-plugin-next": "16.4.0-canary.29",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-internal/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@next/eslint-plugin-internal",
"private": true,
"version": "16.4.0-canary.28",
"version": "16.4.0-canary.29",
"description": "ESLint plugin for working on Next.js.",
"exports": {
".": "./src/eslint-plugin-internal.js"
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
"version": "16.4.0-canary.28",
"version": "16.4.0-canary.29",
"description": "ESLint plugin for Next.js.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/font/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@next/font",
"private": true,
"version": "16.4.0-canary.28",
"version": "16.4.0-canary.29",
"repository": {
"url": "vercel/next.js",
"directory": "packages/font"
Expand Down
2 changes: 1 addition & 1 deletion packages/next-bundle-analyzer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
"version": "16.4.0-canary.28",
"version": "16.4.0-canary.29",
"main": "index.js",
"types": "index.d.ts",
"license": "MIT",
Expand Down
11 changes: 11 additions & 0 deletions packages/next-codemod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,14 @@ Codemods are transformations that run on your codebase programmatically. This al
## Documentation

Visit [nextjs.org/docs/advanced-features/codemods](https://nextjs.org/docs/app/guides/upgrading/codemods) to view the documentation for this package.

## Skip optional feature adoption

`upgrade --skip-adoption` skips codemods marked as feature adoption in the
registry while keeping version migrations and normal dependency selection.
Currently this excludes `cache-components-instant-false` and the
`remove-partial-prefetch` cleanup used after adopting partial prefetching.
Both transforms can still be run explicitly.

Combine it with `--yes` for an unattended version upgrade. Without
`--skip-adoption`, the existing upgrade selections are unchanged.
5 changes: 5 additions & 0 deletions packages/next-codemod/bin/next-codemod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ program
'Skip every interactive prompt and accept its default. Also auto-enabled when stdin is not a TTY (e.g. running under an agent or in CI).',
false
)
.option(
'--skip-adoption',
'Skip optional feature-adoption codemods while applying version migrations.',
false
)
.action(async (revision, options) => {
try {
await runUpgrade(revision, options)
Expand Down
10 changes: 6 additions & 4 deletions packages/next-codemod/bin/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ function resolveSemanticRevision(

export async function runUpgrade(
revision: string | undefined,
options: { verbose: boolean; yes?: boolean }
options: { verbose: boolean; yes?: boolean; skipAdoption?: boolean }
): Promise<void> {
const { verbose } = options
const nonInteractive = options.yes === true || !process.stdin.isTTY
Expand Down Expand Up @@ -272,7 +272,8 @@ export async function runUpgrade(
const codemods = await suggestCodemods(
installedNextVersion,
targetNextVersion,
nonInteractive
nonInteractive,
options.skipAdoption
)
const packageManager: PackageManager = getPkgManager(cwd)

Expand Down Expand Up @@ -641,7 +642,8 @@ async function suggestTurbopack(
async function suggestCodemods(
initialNextVersion: string,
targetNextVersion: string,
nonInteractive: boolean
nonInteractive: boolean,
skipAdoption = false
): Promise<string[]> {
// example:
// codemod version: 15.0.0-canary.45
Expand Down Expand Up @@ -670,7 +672,7 @@ async function suggestCodemods(
const relevantCodemods = TRANSFORMER_INQUIRER_CHOICES.slice(
initialVersionIndex,
targetVersionIndex
)
).filter((codemod) => !skipAdoption || !codemod.adoption)

if (relevantCodemods.length === 0) {
return []
Expand Down
5 changes: 5 additions & 0 deletions packages/next-codemod/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { yellow } from 'picocolors'
import isGitClean from 'is-git-clean'

export const NEXT_CODEMOD_ERROR_PREFIX = '@next-codemod-error'
export const NEXT_CODEMOD_IGNORE_ERROR_PREFIX = '@next-codemod-ignore'

export function checkGitStatus(force) {
let clean = false
let errorMessage = 'Unable to determine if git directory is clean'
Expand Down Expand Up @@ -148,11 +151,13 @@ export const TRANSFORMER_INQUIRER_CHOICES = [
'Add `export const instant = false` to App Router pages and layouts to ease Cache Components adoption',
value: 'cache-components-instant-false',
version: '16.3.0',
adoption: true,
},
{
title:
"Remove `export const prefetch = 'partial'` Route Segment Config from App Router pages and layouts after enabling `partialPrefetching` globally",
value: 'remove-partial-prefetch',
version: '16.3.0',
adoption: true,
},
]
2 changes: 1 addition & 1 deletion packages/next-codemod/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
"version": "16.4.0-canary.28",
"version": "16.4.0-canary.29",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-nocheck
// TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
// @next-codemod-ignore Cache Components adoption: this segment temporarily allows blocking.
// Remove this opt-out after verifying the segment passes validation without it.
// See: https://nextjs.org/docs/app/guides/migrating-to-cache-components
export const instant = false;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-nocheck
// TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
// @next-codemod-ignore Cache Components adoption: this segment temporarily allows blocking.
// Remove this opt-out after verifying the segment passes validation without it.
// See: https://nextjs.org/docs/app/guides/migrating-to-cache-components
export const instant = false;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-nocheck
// TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
// @next-codemod-ignore Cache Components adoption: this segment temporarily allows blocking.
// Remove this opt-out after verifying the segment passes validation without it.
// See: https://nextjs.org/docs/app/guides/migrating-to-cache-components
export const instant = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* A page with a leading JSDoc banner.
* The opt-out must be appended after this block, not inside it.
*/
// TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
// @next-codemod-ignore Cache Components adoption: this segment temporarily allows blocking.
// Remove this opt-out after verifying the segment passes validation without it.
// See: https://nextjs.org/docs/app/guides/migrating-to-cache-components
export const instant = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
bar,
} from './lib'

// TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
// @next-codemod-ignore Cache Components adoption: this segment temporarily allows blocking.
// Remove this opt-out after verifying the segment passes validation without it.
// See: https://nextjs.org/docs/app/guides/migrating-to-cache-components
export const instant = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import { Suspense } from 'react'
import { foo } from './bar'

// TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
// @next-codemod-ignore Cache Components adoption: this segment temporarily allows blocking.
// Remove this opt-out after verifying the segment passes validation without it.
// See: https://nextjs.org/docs/app/guides/migrating-to-cache-components
export const instant = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { use } from "react";
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers';

export function MyComponent2() {
(draftMode() as unknown as UnsafeUnwrappedDraftMode).enable()
(/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedDraftMode cast after repairing the migration. */
draftMode() as unknown as UnsafeUnwrappedDraftMode).enable()
}

export function useDraftModeEnabled() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'

export function MyComponent2() {
(draftMode() as unknown as UnsafeUnwrappedDraftMode).enable()
(/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedDraftMode cast after repairing the migration. */
draftMode() as unknown as UnsafeUnwrappedDraftMode).enable()
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers';

export function MyComp() {
return (headers() as unknown as UnsafeUnwrappedHeaders);
return (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedHeaders cast after repairing the migration. */
headers() as unknown as UnsafeUnwrappedHeaders);
}

export function MyComp2() {
return (headers() as unknown as UnsafeUnwrappedHeaders);
return (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedHeaders cast after repairing the migration. */
headers() as unknown as UnsafeUnwrappedHeaders);
}

export function MyComp3() {
return (headers() as unknown as UnsafeUnwrappedHeaders);
return (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedHeaders cast after repairing the migration. */
headers() as unknown as UnsafeUnwrappedHeaders);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers';

export function MyComp() {
void (headers() as unknown as UnsafeUnwrappedHeaders)
void (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedHeaders cast after repairing the migration. */
headers() as unknown as UnsafeUnwrappedHeaders)
}

export function generateContentfulMetadata() {
void (headers() as unknown as UnsafeUnwrappedHeaders)
void (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedHeaders cast after repairing the migration. */
headers() as unknown as UnsafeUnwrappedHeaders)
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function myFun() {

export function myFun2() {
return function () {
void (headers() as unknown as UnsafeUnwrappedHeaders)
void (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedHeaders cast after repairing the migration. */
headers() as unknown as UnsafeUnwrappedHeaders)
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers';

export function myFunc() {
const c = (cookies() as unknown as UnsafeUnwrappedCookies)
void (cookies() as unknown as UnsafeUnwrappedCookies)
const c = (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedCookies cast after repairing the migration. */
cookies() as unknown as UnsafeUnwrappedCookies)
void (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedCookies cast after repairing the migration. */
cookies() as unknown as UnsafeUnwrappedCookies)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,23 @@ import {
} from 'next/headers';

export function MyDraftComponent() {
if ((draftMode() as unknown as UnsafeUnwrappedDraftMode).isEnabled) {
if ((/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedDraftMode cast after repairing the migration. */
draftMode() as unknown as UnsafeUnwrappedDraftMode).isEnabled) {
return null
}

return <p>page</p>
}

export function MyCookiesComponent() {
const c = (cookies() as unknown as UnsafeUnwrappedCookies)
const c = (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedCookies cast after repairing the migration. */
cookies() as unknown as UnsafeUnwrappedCookies)
return c.get('name')
}

export function MyHeadersComponent() {
const h = (headers() as unknown as UnsafeUnwrappedHeaders)
const h = (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedHeaders cast after repairing the migration. */
headers() as unknown as UnsafeUnwrappedHeaders)
return <p>{h.get('x-foo')}</p>
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers';

export default function Foo(): string {
const name = (cookies() as unknown as UnsafeUnwrappedCookies).get('name')
const name = (/* @next-codemod-error Await this API and update its callers; remove the temporary UnsafeUnwrappedCookies cast after repairing the migration. */
cookies() as unknown as UnsafeUnwrappedCookies).get('name')
return name
}
Loading
Loading