Relevant source files
The following files were used as context for generating this wiki page:
- packages/next-codemod/bin/transform.ts
- packages/next-codemod/bin/upgrade.ts
- packages/next-codemod/bin/next-codemod.ts
- packages/next-codemod/transforms/next-lint-to-eslint-cli.ts
- packages/next-codemod/transforms/next-image-experimental.ts
- packages/next-codemod/transforms/url-to-withrouter.ts
- packages/next-codemod/transforms/cra-to-next.ts
- packages/next-codemod/lib/utils.ts
- packages/next-codemod/transforms/next-image-to-legacy-image.ts
- packages/next-codemod/transforms/middleware-to-proxy.ts
- packages/next-codemod/transforms/next-experimental-turbo-to-turbopack.ts
- packages/next-codemod/transforms/remove-unstable-prefix.ts
- packages/next-codemod/lib/run-jscodeshift.ts
- packages/next-codemod/transforms/new-link.ts
- packages/next/src/cli/next-upgrade.ts
- packages/next-codemod/transforms/lib/async-request-api/index.ts
- packages/next-codemod/transforms/next-async-request-api.ts
- packages/next-codemod/transforms/lib/async-request-api/next-async-dynamic-prop.ts
- packages/next-codemod/transforms/next-og-import.ts
- packages/next-codemod/package.json
- packages/next-codemod/transforms/built-in-next-font.ts
- packages/next/src/build/duration-to-string.ts
- packages/next/src/server/config.ts
Overview
The @next/codemod package provides automated AST transformation pipelines and upgrade tooling designed to modernize Next.js codebases, update deprecated configurations, and safely migrate dependencies across major framework releases. By combining programmatic Abstract Syntax Tree (AST) manipulation via jscodeshift with interactive CLI workflows, it eliminates manual refactoring overhead when adopting breaking changes packages/next-codemod/bin/next-codemod.ts:12-47, packages/next-codemod/bin/transform.ts:25-242.
Sources: packages/next-codemod/bin/next-codemod.ts:12-47, packages/next-codemod/bin/transform.ts:25-242]
Upgrade CLI and Telemetry Orchestration
Call-Chain Execution Walkthrough
Project upgrades and automated telemetry reporting are orchestrated through CLI entrypoints that spawn child processes, validate project structures, and load user configurations without strict schema validation against current Next.js rules packages/next/src/cli/next-upgrade.ts:143-150. The spawnNextUpgrade function bootstraps the upgrade flow by validating dependencies, resolving project paths, and optionally handling agent-driven upgrades packages/next/src/cli/next-upgrade.ts:177-184. Upgrade telemetry events track invocation outcomes, metadata failures, guide preparations, and agent results through bounded identifiers and status codes packages/next/src/cli/next-upgrade.ts:23-30, packages/next/src/cli/next-upgrade.ts:639-652.
Sources: packages/next/src/cli/next-upgrade.ts:23-30, packages/next/src/cli/next-upgrade.ts:143-150, packages/next/src/cli/next-upgrade.ts:177-184, packages/next/src/cli/next-upgrade.ts:639-652]
The upgrade result reporting and configuration loading pipeline follows a strict execution sequence from incoming telemetry handlers down to low-level duration string formatting and React version inspection.
reportAgentUpgradeAgentResult— Validates the upgrade run UUID againstUUID_PATTERNand checks that the result is strictlysuccessorfailurebefore initializing telemetry packages/next/src/cli/next-upgrade.ts:699-712.loadAgentUpgradeConfig— Invokes configuration loading withPHASE_PRODUCTION_BUILDand raw config flags enabled packages/next/src/cli/next-upgrade.ts:143-150.loadConfig— Measures high-resolution timing via BigInt nanoseconds when logging is enabled and calls the underlying implementation packages/next/src/server/config.ts:1938-1962.loadConfigImpl— Resolves cache keys usinggetCacheKey, checksconfigCache, loads environment configs, and applies validation rules packages/next/src/server/config.ts:1980-2089.finalizeConfig— Applies post-processing mutations for experimental features, output hashes, and asset token support packages/next/src/server/config.ts:1813-1862.
Sources: packages/next/src/cli/next-upgrade.ts:143-150, packages/next/src/cli/next-upgrade.ts:699-712, packages/next/src/server/config.ts:1813-1862, packages/next/src/server/config.ts:1938-1962, packages/next/src/server/config.ts:1980-2089]
Note
reportAgentUpgradeAgentResult validates that project details and error text never enter telemetry payloads, accepting only a bounded run UUID and binary success or failure outcomes packages/next/src/cli/next-upgrade.ts:703-711.
An alternative execution path executes duration formatting and package version inspections during initialization:
reportAgentUpgradeAgentResult— Receives the run identifier and completion status packages/next/src/cli/next-upgrade.ts:699-702.loadAgentUpgradeConfig— Reads raw configuration without enforcing current schema constraints packages/next/src/cli/next-upgrade.ts:143-150.loadConfig— Wraps implementation execution and computes configuration load duration packages/next/src/server/config.ts:1938-1972.hrtimeBigIntDurationToString— Converts high-resolution BigInt nanosecond durations into human-readable strings packages/next/src/build/duration-to-string.ts:86-88.durationToStringWithNanoseconds— Determines the magnitude tier (minutes, seconds, or milliseconds) and formats precision accordingly packages/next/src/build/duration-to-string.ts:53-66.
Sources: packages/next/src/cli/next-upgrade.ts:143-150, packages/next/src/cli/next-upgrade.ts:699-702, packages/next/src/build/duration-to-string.ts:53-66, packages/next/src/build/duration-to-string.ts:86-88, packages/next/src/server/config.ts:1938-1972]
A third trace branch handles React deprecation checks during configuration loading:
reportAgentUpgradeAgentResult— Initiates the telemetry reporting sequence packages/next/src/cli/next-upgrade.ts:699-702.loadAgentUpgradeConfig— Loads the unconstrained app configuration packages/next/src/cli/next-upgrade.ts:143-150.loadConfig— CallsloadConfigImpland subsequently triggers React version warnings packages/next/src/server/config.ts:1938-1964.warnIfReact18IsInstalled— Inspects development server or production build phases for React 18 installations packages/next/src/server/config.ts:81-98.getInstalledPackageVersion— Resolves the package.json path usingcreateRequireand extracts the installed version string packages/next/src/server/config.ts:72-79.
Sources: packages/next/src/cli/next-upgrade.ts:143-150, packages/next/src/cli/next-upgrade.ts:699-702, packages/next/src/server/config.ts:72-79, packages/next/src/server/config.ts:81-98, packages/next/src/server/config.ts:1938-1964]
Sources: packages/next/src/cli/next-upgrade.ts:143-150, packages/next/src/cli/next-upgrade.ts:699-702, packages/next/src/build/duration-to-string.ts:53-88, packages/next/src/server/config.ts:1938-1964]
Interactive Upgrade Assistant Flow
Semantic Revision Resolution and Version Inspection
The upgrade assistant inspects installed project versions by parsing local package.json files and invoking package resolution helpers. getInstalledNextVersion() and getInstalledReactVersion() resolve package paths relative to the current working directory (cwd), extracting version strings from next/package.json and react/package.json. If version detection fails, a BadInput error prompts users running inside monorepos to execute commands directly from the Next.js app directory.
function getInstalledNextVersion(): string {
try {
return require(
require.resolve('next/package.json', {
paths: [cwd],
})
).version
} catch (error) {
throw new BadInput(
`Failed to get the installed Next.js version at "${cwd}".\nIf you're using a monorepo, please run this command from the Next.js app directory.`,
{
cause: error,
}
)
}
}
function getInstalledReactVersion(): string {
try {
return require(
require.resolve('react/package.json', {
paths: [cwd],
})
).version
} catch (error) {
throw new BadInput(
`Failed to detect the installed React version in "${cwd}".\nIf you're working in a monorepo, please run this command from the Next.js app directory.`,
{
cause: error,
}
)
}
}Sources: packages/next-codemod/bin/upgrade.ts:558-590
Semantic version keywords supplied via the CLI revision argument are mapped through resolveSemanticRevision() to generate explicit npm version queries based on the currently installed major and minor version numbers.
| Revision Keyword | Resolution Behavior | Generated Query |
|---|---|---|
patch | Targets the latest patch within the current minor version | ~${installedMajor}.${installedMinor}.0 |
minor | Targets the latest minor within the current major version | ^${installedMajor}.0.0 |
major | Targets the latest stable release | latest |
| custom/other | Preserves the provided revision string verbatim | revision |
function resolveSemanticRevision(
revision: string,
installedVersion: string
): string {
const installedMajor = major(installedVersion)
const installedMinor = minor(installedVersion)
switch (revision) {
case 'patch':
// ~15.0.0 matches >=15.0.0 <15.1.0
return `~${installedMajor}.${installedMinor}.0`
case 'minor':
// ^15.0.0 matches >=15.0.0 <16.0.0
return `^${installedMajor}.0.0`
case 'major':
return 'latest'
default:
return revision
}
}Sources: packages/next-codemod/bin/upgrade.ts:93-112
Note
loadHighestNPMVersionMatching() executes synchronous npm view queries with JSON output enabled to validate React and Next.js compatibility ranges, sorting matching versions with semver comparison utilities to select the highest valid release.
async function loadHighestNPMVersionMatching(query: string) {
const versionsJSON = execSync(
`npm --silent view "${query}" --json --field version`,
{ encoding: 'utf-8' }
)
const versionOrVersions = JSON.parse(versionsJSON)
if (versionOrVersions.length < 1) {
throw new Error(
`Found no React versions matching "${query}". This is a bug in the upgrade tool.`
)
}
// npm-view returns an array if there are multiple versions matching the query.
if (Array.isArray(versionOrVersions)) {
// The last entry will be the latest version published.
// But we want the highest version.
versionOrVersions.sort(compareVersions)
return versionOrVersions[versionOrVersions.length - 1]
}
return versionOrVersions
}Sources: packages/next-codemod/bin/upgrade.ts:46-65
Turbopack and Codemod Heuristics
Project upgrade flows evaluate directory structures and script definitions using automated heuristics. isUsingPagesDir() and isUsingAppDir() check for the presence of pages or app directories at the project root or inside src/.
function isUsingPagesDir(projectPath: string): boolean {
return (
fs.existsSync(path.resolve(projectPath, 'pages')) ||
fs.existsSync(path.resolve(projectPath, 'src/pages'))
)
}
function isUsingAppDir(projectPath: string): boolean {
return (
fs.existsSync(path.resolve(projectPath, 'app')) ||
fs.existsSync(path.resolve(projectPath, 'src/app'))
)
}Sources: packages/next-codemod/bin/upgrade.ts:592-603
Turbopack adoption is guided by inspecting the dev script in package.json. Depending on the target Next.js version, the assistant selects between --turbo and --turbopack flags based on the 15.0.1-canary.3 release threshold.
async function suggestTurbopack(
packageJson: any,
targetNextVersion: string,
nonInteractive: boolean
): Promise<void> {
const devScript: string | undefined = packageJson.scripts?.['dev']
// Turbopack flag was changed from `--turbo` to `--turbopack` in v15.0.1-canary.3
// PR: https://github.com/vercel/next.js/pull/71657
// Release: https://github.com/vercel/next.js/releases/tag/v15.0.1-canary.3
const isAfterTurbopackFlagChange =
compareVersions(targetNextVersion, '15.0.1-canary.3') >= 0
const turboPackFlag = isAfterTurbopackFlagChange ? '--turbopack' : '--turbo'Sources: packages/next-codemod/bin/upgrade.ts:616-627
Caution
The Turbopack CLI flag was renamed from --turbo to --turbopack starting in version 15.0.1-canary.3; target version comparisons ensure backward-compatible script generation.
// Turbopack flag was changed from `--turbo` to `--turbopack` in v15.0.1-canary.3
// PR: https://github.com/vercel/next.js/pull/71657
// Release: https://github.com/vercel/next.js/releases/tag/v15.0.1-canary.3
const isAfterTurbopackFlagChange =
compareVersions(targetNextVersion, '15.0.1-canary.3') >= 0
const turboPackFlag = isAfterTurbopackFlagChange ? '--turbopack' : '--turbo'Sources: packages/next-codemod/bin/upgrade.ts:622-627
Available codemods and framework migrations are enumerated in TRANSFORMER_INQUIRER_CHOICES, tracking specific target versions and adoption flags across releases ranging from legacy URL transforms to modern cache component configurations.
export const TRANSFORMER_INQUIRER_CHOICES = [
{
title:
'Transform the deprecated automatically injected url property on top level pages to using withRouter',
value: 'url-to-withrouter',
version: '6.0.0',
},
{
title:
'Transforms anonymous components into named components to make sure they work with Fast Refresh',
value: 'name-default-component',
version: '9.0.0',
},
{
title:
'Transforms files that do not import `React` to include the import in order for the new React JSX transform',
value: 'add-missing-react-import',
version: '10.0.0',
},
{
title:
'Automatically migrates a Create React App project to Next.js (experimental)',
value: 'cra-to-next',
version: '11.0.0',
},
{
title: 'Ensures your <Link> usage is backwards compatible',
value: 'new-link',
version: '13.0.0',
},
{
title:
'Dangerously migrates from `next/legacy/image` to the new `next/image` by adding inline styles and removing unused props (experimental)',
value: 'next-image-experimental',
version: '13.0.0',
},
{
title:
'Safely migrate Next.js 10, 11, 12 applications importing `next/image` to the renamed `next/legacy/image` import in Next.js 13',
value: 'next-image-to-legacy-image',
version: '13.0.0',
},
{
title: 'Uninstall `@next/font` and transform imports to `next/font`',
value: 'built-in-next-font',
version: '13.2.0',
},
{
title:
'Migrates certain viewport related metadata from the `metadata` export to a new `viewport` export',
value: 'metadata-to-viewport-export',
version: '14.0.0',
},
{
title:
'Transforms imports from `next/server` to `next/og` for usage of Dynamic OG Image Generation',
value: 'next-og-import',
version: '14.0.0',
},
{
title:
'Install `@vercel/functions` to replace `geo` and `ip` properties on `NextRequest`',
value: 'next-request-geo-ip',
version: '15.0.0-canary.153',
},
{
title: 'Transforms usage of Next.js async Request APIs',
value: 'next-async-request-api',
version: '15.0.0-canary.171',
},
{
title:
'Transform App Router Route Segment Config `runtime` value from `experimental-edge` to `edge`',
value: 'app-dir-runtime-config-experimental-edge',
version: '15.0.0-canary.179',
},
{
title: 'Updates `next.config.js` to use the new `turbopack` configuration',
value: 'next-experimental-turbo-to-turbopack',
version: '15.4.2-canary.21',Sources: packages/next-codemod/lib/utils.ts:47-163
Codemod Runner and Execution Pipeline
Git Working Tree Validation and Path Expansion
The execution pipeline initializes by validating the state of the local git working tree via checkGitStatus(). Unless the --dry or --force flags are active, isGitClean.sync() inspects the working directory to confirm there are no uncommitted changes. If the repository is dirty and force validation is omitted, the process terminates immediately with an exit code of 1.
if (!options.dry) {
checkGitStatus(options.force)
}export function checkGitStatus(force) {
let clean = false
let errorMessage = 'Unable to determine if git directory is clean'
try {
clean = isGitClean.sync(process.cwd())
errorMessage = 'Git directory is not clean'
} catch (err) {
if (err && err.stderr && err.stderr.includes('Not a git repository')) {
clean = true
}
}
if (!clean) {
if (force) {
console.log(`WARNING: ${errorMessage}. Forcibly continuing.`)
} else {
console.log('Thank you for using @next/codemod!')
console.log(
yellow(
'\nBut before we continue, please stash or commit your git changes.'
)
)
console.log(
'\nYou may use the --force flag to override this safety check.'
)
process.exit(1)
}
}
}Sources: packages/next-codemod/bin/transform.ts:33-35, packages/next-codemod/lib/utils.ts:7-35
Warning
If a directory is not a valid git repository, checkGitStatus() catches the Not a git repository error from standard error streams and treats the directory as clean, allowing execution to proceed outside version control.
} catch (err) {
if (err && err.stderr && err.stderr.includes('Not a git repository')) {
clean = true
}
}Sources: packages/next-codemod/lib/utils.ts:13-17
When file paths or glob patterns are supplied to runTransform(), expandFilePathsIfNeeded() inspects the argument array for wildcard characters (*). If found, it invokes synchronous globby.sync() to expand paths into concrete file targets across the disk.
function expandFilePathsIfNeeded(filesBeforeExpansion) {
const shouldExpandFiles = filesBeforeExpansion.some((file) =>
file.includes('*')
)
return shouldExpandFiles
? globby.sync(filesBeforeExpansion)
: filesBeforeExpansion
}Sources: packages/next-codemod/bin/transform.ts:13-20
JSCodeshift Invocation and Output Parsing
Codemods that do not execute custom handlers directly are delegated to the jscodeshift executable via execa. runTransform() assembles command-line arguments based on user-supplied options, applying standard parsers, ignore patterns, and file extensions.
| Option Flag | Argument Value Generated | Purpose |
|---|---|---|
dry | --dry | Simulates the transform without modifying files |
print | --print | Outputs transformed file contents directly to stdout |
runInBand | --run-in-band | Serializes transform execution across files |
verbose | --verbose=2 | Increases logging verbosity level |
| default | --parser=tsx | Sets default AST parser to tsx |
| default | --ignore-pattern=**/node_modules/** | Excludes dependency directories |
| default | --ignore-pattern=**/.next/** | Excludes build output directories |
| default | --extensions=tsx,ts,jsx,js,mjs | Restricts transformation targets to JS/TS extensions |
const { dry, print, runInBand, jscodeshift, verbose } = options
if (dry) {
args.push('--dry')
}
if (print) {
args.push('--print')
}
if (runInBand) {
args.push('--run-in-band')
}
if (verbose) {
args.push('--verbose=2')
}
args.push('--parser=tsx')
args.push('--ignore-pattern=**/node_modules/**')
args.push('--ignore-pattern=**/.next/**')
args.push('--extensions=tsx,ts,jsx,js,mjs')
Sources: packages/next-codemod/bin/transform.ts:123-143
During execution, execaChildProcess.stdout streams character chunks into a sliding window buffer (lastThreeLineBreaks) retaining the last three line breaks. This buffer isolates the final success summary line matching the pattern N ok.
execaChildProcess.stdout.on('data', (chunk) => {
lastThreeLineBreaks += chunk.toString('utf-8')
let cutoff = lastThreeLineBreaks.length
// Note: the stdout ends with "\n".
// "foo\n" + "bar\n" + "baz\n" -> "\nbar\nbaz\n"
// "\n" + "foo\n" + "bar\n" -> "\nfoo\nbar\n"
for (let i = 0; i < 3; i++) {
cutoff = lastThreeLineBreaks.lastIndexOf('\n', cutoff) - 1
}
if (cutoff > 0 && cutoff < lastThreeLineBreaks.length) {
lastThreeLineBreaks = lastThreeLineBreaks.slice(cutoff + 1)
}
})Sources: packages/next-codemod/bin/transform.ts:173-189
const targetOkLine = lastThreeLineBreaks.split('\n').at(-3)
if (!targetOkLine.endsWith('ok')) {
throw new Error(
`Failed to parse the successful transformation count "${targetOkLine}". This is a bug in the codemod tool.`
)
}
const stripped = stripAnsi(targetOkLine)
// "N ok" -> "N"
const parsedNum = parseInt(stripped.split(' ')[0], 10)
const hasChanges = parsedNum > 0Sources: packages/next-codemod/bin/transform.ts:204-215
Async Request API Migration Transforms
Dynamic Property and API Transformation Pipeline
The async request API transform orchestrator executes a sequential pipeline composed of dynamic property transforms and dynamic API transforms. Given a source file and an API instance from jscodeshift, the runner reduces over the transformation functions, passing the file path and updating the source string incrementally when changes occur.
export default function transform(file: FileInfo, api: API) {
const transforms = [transformDynamicProps, transformDynamicAPI]
return transforms.reduce<string>((source, transformFn) => {
const result = transformFn(source, api, file.path)
if (!result) {
return source
}
return result
}, file.source)
}Sources: packages/next-codemod/transforms/lib/async-request-api/index.ts:5-15
export { default } from './lib/async-request-api/index'Sources: packages/next-codemod/transforms/next-async-request-api.ts:1-1
Member Access Awaiting and React Use Integration
For dynamic properties, awaitMemberAccessOfProp() scans the target function body for member expressions matching identifier names like props. When an access matches target property names, the transform checks parent scopes and expressions to determine if an await expression can be inserted. If a member access parent scope is synchronous and distinct from the function itself, conversion is blocked and an error comment prefix (NEXT_CODEMOD_ERROR_PREFIX) is injected into the node to warn the developer.
function awaitMemberAccessOfProp(
propIdName: string,
path: ASTPath<FunctionScope>,
j: API['jscodeshift']
) {
// search the member access of the prop
const functionBody = findFunctionBody(path)
const memberAccess = j(functionBody).find(j.MemberExpression, {
object: {
type: 'Identifier',
name: propIdName,
},
})
let hasAwaited = false
// await each member access
memberAccess.forEach((memberAccessPath) => {
const member = memberAccessPath.value
const memberProperty = member.property
const isAccessingMatchedProperty =
j.Identifier.check(memberProperty) &&
TARGET_PROP_NAMES.has(memberProperty.name)
if (!isAccessingMatchedProperty) {
return
}
if (isParentPromiseAllCallExpression(memberAccessPath, j)) {
return
}
// check if it's already awaited
if (memberAccessPath.parentPath?.value.type === 'AwaitExpression') {
return
}
const parentScopeOfMemberAccess = findClosetParentFunctionScope(
memberAccessPath,
j
)
// When the parent scope is sync, and it's also not the function itself, which means it's not able to convert to async.
if (
parentScopeOfMemberAccess &&
!parentScopeOfMemberAccess.value?.async &&
parentScopeOfMemberAccess.node !== path.node
) {
// If it's not able to convert, add a comment to the prop access to warn the user
// e.g. the parent scope is sync, await keyword can't be applied
const comment = ` ${NEXT_CODEMOD_ERROR_PREFIX} '${propIdName}.${memberProperty.name}' is accessed without awaiting.`
insertCommentOnce(member, j, comment)
return
}
const awaitedExpr = j.awaitExpression(member)
const awaitMemberAccess = wrapParentheseIfNeeded(true, j, awaitedExpr)
memberAccessPath.replace(awaitMemberAccess)
hasAwaited = true
})Sources: packages/next-codemod/transforms/lib/async-request-api/next-async-dynamic-prop.ts:37-97
Warning
If a parent function scope is synchronous and cannot be converted to async, awaitMemberAccessOfProp() skips replacement and injects a warning comment directly into the source code rather than throwing an unhandled exception.
const parentScopeOfMemberAccess = findClosetParentFunctionScope(
memberAccessPath,
j
)
// When the parent scope is sync, and it's also not the function itself, which means it's not able to convert to async.
if (
parentScopeOfMemberAccess &&
!parentScopeOfMemberAccess.value?.async &&
parentScopeOfMemberAccess.node !== path.node
) {
// If it's not able to convert, add a comment to the prop access to warn the user
// e.g. the parent scope is sync, await keyword can't be applied
const comment = ` ${NEXT_CODEMOD_ERROR_PREFIX} '${propIdName}.${memberProperty.name}' is accessed without awaiting.`
insertCommentOnce(member, j, comment)
return
}Sources: packages/next-codemod/transforms/lib/async-request-api/next-async-dynamic-prop.ts:74-90
Alternatively, applyUseAndRenameAccessedProp() rewrites synchronous property accesses to leverage React's use() hook. It traverses member expressions on the property identifier, records accessed member names, replaces member expressions with their property identifiers, and unshifts a const variable declaration invoking use() on the accessed property directly into the function body scope.
function applyUseAndRenameAccessedProp(
propIdName: string,
path: ASTPath<FunctionScope>,
j: API['jscodeshift']
) {
// search the member access of the prop, and rename the member access to the member value
// e.g.
// props.params => params
// props.params.foo => params.foo
// props.searchParams.search => searchParams.search
let modified = false
const functionBody = findFunctionBody(path)
const memberAccess = j(functionBody).find(j.MemberExpression, {
object: {
type: 'Identifier',
name: propIdName,
},
})
const accessedNames: string[] = []
// rename each member access
memberAccess.forEach((memberAccessPath) => {
// If the member access expression is first argument of `use()`, we skip
if (isParentUseCallExpression(memberAccessPath, j)) {
return
}
const member = memberAccessPath.value
const memberProperty = member.property
if (j.Identifier.check(memberProperty)) {
accessedNames.push(memberProperty.name)
} else if (j.MemberExpression.check(memberProperty)) {
let currentMember = memberProperty
if (j.Identifier.check(currentMember.object)) {
accessedNames.push(currentMember.object.name)
}
}
memberAccessPath.replace(memberProperty)
})
// If there's any renamed member access, need to call `use()` onto member access
// e.g. ['params'] => insert `const params = use(props.params)`
if (accessedNames.length > 0) {
const accessedPropId = j.identifier(propIdName)
const accessedProp = j.memberExpression(
accessedPropId,
j.identifier(accessedNames[0])
)
const useCall = j.callExpression(j.identifier('use'), [accessedProp])
const useDeclaration = j.variableDeclaration('const', [
j.variableDeclarator(j.identifier(accessedNames[0]), useCall),
])
if (functionBody) {
functionBody.unshift(useDeclaration)
}
modified = true
}
return modified
}Sources: packages/next-codemod/transforms/lib/async-request-api/next-async-dynamic-prop.ts:114-175
Core Next.js Configuration Transforms
Middleware Route Proxy and Configuration Mapping
Next.js codemods automate the renaming of legacy middleware configuration properties, type imports, and file structures to their proxy equivalents. The middleware-to-proxy transform scans configuration files, middleware modules, and files importing from next/server to replace deprecated identifiers. Specifically, CONFIG_PROPERTY_MAP translates four legacy configuration properties, while MIDDLEWARE_TYPE_IMPORT_MAP maps server types to their updated counterparts.
| Legacy Property | New Proxy Property | Sources |
|---|---|---|
middlewarePrefetch | proxyPrefetch | packages/next-codemod/transforms/middleware-to-proxy.ts:21-21 |
middlewareClientMaxBodySize | proxyClientMaxBodySize | packages/next-codemod/transforms/middleware-to-proxy.ts:22-22 |
externalMiddlewareRewritesResolve | externalProxyRewritesResolve | packages/next-codemod/transforms/middleware-to-proxy.ts:23-23 |
skipMiddlewareUrlNormalize | skipProxyUrlNormalize | packages/next-codemod/transforms/middleware-to-proxy.ts:24-24 |
Sources: packages/next-codemod/transforms/middleware-to-proxy.ts:19-25
| Legacy Type Import | New Type Import | Sources |
|---|---|---|
NextMiddleware | NextProxy | packages/next-codemod/transforms/middleware-to-proxy.ts:29-29 |
MiddlewareConfig | ProxyConfig | packages/next-codemod/transforms/middleware-to-proxy.ts:30-30 |
Sources: packages/next-codemod/transforms/middleware-to-proxy.ts:27-31
Warning
When processing a middleware file, the transformer removes runtime segment configurations entirely and unlinks the original middleware file after writing the new proxy file output.
Sources: packages/next-codemod/transforms/middleware-to-proxy.ts:63-86
Turbopack Options and Unstable Cache Transforms
Configuration transforms also migrate experimental Turbopack options and unstable prefix APIs. The next-experimental-turbo-to-turbopack codemod shifts properties from experimental.turbo to the top-level turbopack configuration object. Special options are mapped to experimental.turbopack* properties, while unsupported memory limits are dropped. Similarly, the remove-unstable-prefix codemod updates stable caching imports from next/cache.
| Renamed Experimental Property | New Experimental Target | Sources |
|---|---|---|
minify | turbopackMinify | packages/next-codemod/transforms/next-experimental-turbo-to-turbopack.ts:30-30 |
treeShaking | turbopackModuleFragments | packages/next-codemod/transforms/next-experimental-turbo-to-turbopack.ts:31-31 |
sourceMaps | turbopackSourceMaps | packages/next-codemod/transforms/next-experimental-turbo-to-turbopack.ts:32-32 |
Sources: packages/next-codemod/transforms/next-experimental-turbo-to-turbopack.ts:28-33
Caution
The memoryLimit option is entirely unsupported and is deleted from both experimental.turbo.memoryLimit and experimental.turbopackMemoryLimit during the transformation pass.
Sources: packages/next-codemod/transforms/next-experimental-turbo-to-turbopack.ts:35-40
| Unstable API | Stable Counterpart | Sources |
|---|---|---|
unstable_cacheTag | cacheTag | packages/next-codemod/transforms/remove-unstable-prefix.ts:7-7 |
unstable_cacheLife | cacheLife | packages/next-codemod/transforms/remove-unstable-prefix.ts:8-8 |
Sources: packages/next-codemod/transforms/remove-unstable-prefix.ts:6-9
Component and Tooling Codemods
Image and Link Component Transformers
Next.js provides automated AST transforms to upgrade legacy components, font packages, OpenGraph generation imports, tool configurations, and entire application structures. These codemods handle syntax migrations for images, links, fonts, ImageResponse, ESLint flat configurations, and Create React App migrations.
The image codemods convert between next/image, next/legacy/image, and next/future/image. The next-image-to-legacy-image transformer replaces standard and future image imports with next/legacy/image and next/future/image imports with next/image across ES modules, dynamic import() expressions, and CommonJS require() calls. Conversely, next-image-experimental migrates legacy image elements and loader configurations by parsing layout props (intrinsic, responsive, fill, fixed) and mapping them to inline styles, sizes attributes, and custom loaders.
| Legacy Image Import Source | Upgraded Target Source | Sources |
|---|---|---|
next/image | next/legacy/image | packages/next-codemod/transforms/next-image-to-legacy-image.ts:15-20 |
next/future/image | next/image | packages/next-codemod/transforms/next-image-to-legacy-image.ts:37-43 |
Sources: packages/next-codemod/transforms/next-image-to-legacy-image.ts:12-43
The new-link codemod updates next/link usages by stripping deprecated legacyBehavior and passHref attributes. If a <Link> wraps an <a> child element, the transformer lifts unique anchor attributes directly to the <Link> element and unwraps the child anchor tag.
Warning
If a <Link> uses removed legacyBehavior and contains a child that cannot be verified as an anchor element, the codemod bails out of lifting child properties and injects an error comment block into the JSX children.
Sources: packages/next-codemod/transforms/new-link.ts:71-86
Font, OpenGraph, and ESLint Migrations
Additional transforms modernize font package imports, OpenGraph utilities, and project linting setups. The built-in-next-font codemod shifts scoped font imports from @next/font, @next/font/google, and @next/font/local to their built-in next/font, next/font/google, and next/font/local counterparts. The next-og-import transformer extracts ImageResponse imports from next/server into dedicated next/og declarations.
| Legacy Font Package | Modernized Font Import | Sources |
|---|---|---|
@next/font | next/font | packages/next-codemod/transforms/built-in-next-font.ts:16-22 |
@next/font/google | next/font/google | packages/next-codemod/transforms/built-in-next-font.ts:26-33 |
@next/font/local | next/font/local | packages/next-codemod/transforms/built-in-next-font.ts:37-44 |
Sources: packages/next-codemod/transforms/built-in-next-font.ts:13-45
The next-lint-to-eslint-cli transformer scans package.json scripts for next lint execution. When detected, it detects TypeScript configuration via tsconfig.json existence, searches for legacy configuration files (.eslintrc.js, .eslintrc.cjs, .eslintrc.yaml, .eslintrc.yml, .eslintrc.json, .eslintrc), and runs @eslint/migrate-config to generate ESLint flat configuration files (eslint.config.mjs, eslint.config.js, eslint.config.cjs, eslint.config.ts, eslint.config.mts, eslint.config.cts).
Note
When no existing ESLint configuration is found, the transformer automatically generates an eslint.config.mjs file incorporating eslint-config-next/core-web-vitals alongside eslint-config-next/typescript if TypeScript is detected.
Sources: packages/next-codemod/transforms/next-lint-to-eslint-cli.ts:62-64
Sources: packages/next-codemod/transforms/next-lint-to-eslint-cli.ts:1088-1094
The cra-to-next project migrator inspects project root structures for react-scripts or vite dependencies. It validates src/index entry points, converts root render calls into Next.js component wrappers, extracts global CSS imports into _app, and generates _app, _document, and catch-all dynamic page routes ([[...slug]]).
Sources: packages/next-codemod/transforms/cra-to-next.ts:55-79
Sources: packages/next-codemod/transforms/cra-to-next.ts:196-200
Related
On This Page
OverviewUpgrade CLI and Telemetry OrchestrationCall-Chain Execution WalkthroughInteractive Upgrade Assistant FlowSemantic Revision Resolution and Version InspectionTurbopack and Codemod HeuristicsCodemod Runner and Execution PipelineGit Working Tree Validation and Path ExpansionJSCodeshift Invocation and Output ParsingAsync Request API Migration TransformsDynamic Property and API Transformation PipelineMember Access Awaiting and React Use IntegrationCore Next.js Configuration TransformsMiddleware Route Proxy and Configuration MappingTurbopack Options and Unstable Cache TransformsComponent and Tooling CodemodsImage and Link Component TransformersFont, OpenGraph, and ESLint MigrationsRelated