Next.js 18 ▲ Upgrading — Version migration & canary channel management
Next.js upgrades are not a single command. They are a sequence of decisions: which version to target, which codemods to run, which breaking changes to fix manually, and whether to track the stable channel or the canary channel. The stable channel receives the release that next@latest points to. The canary channel receives every commit that passes the test suite, including features that are not yet stable and breaking changes that have not been announced. The two channels serve different purposes, and mixing them without a plan is how a project ends up running unreleased code in production.
This chapter covers the upgrade path from a stable version to the latest stable release, the canary channel’s role and its risks, the automated codemods that handle the mechanical migrations, and the breaking changes that require manual attention in the current major version.
Key point: The next upgrade command handles the version bump and runs the available codemods. It does not fix every breaking change. The middleware rename, the parallel route default.js requirement, and the removal of next lint are manual migrations that the codemod does not cover.
Why the two channels exist
The stable channel problem. A production application needs a version that has been tested, documented, and supported. The stable channel provides that. next@latest resolves to the most recent stable release, and the version guides document the breaking changes between major versions . A stable upgrade is a planned migration with a known set of changes.
The canary channel problem. New features are developed on the canary channel before they are promoted to stable. The canary release is published on every commit, and the documentation describes it as the channel where “features available in canary” are listed . The authentication APIs (forbidden, unauthorized, authInterrupts) are an example of features that are documented as canary-only . A project that adopts canary gets those features early, at the cost of instability and undocumented breaking changes.
The upgrade command problem. The next upgrade command was introduced in Next.js 16.1.0. Before that version, the upgrade path was the @next/codemod package. The current documentation describes both: pnpm next upgrade for 16.1.0 and later, and npx @next/codemod@canary upgrade latest for earlier versions . The command upgrades the package versions and runs the codemods that are available for the migration.
a. The stable upgrade path
The stable upgrade begins with the version check. The current major version requires Node.js 20.9 or later, TypeScript 5.1 or later, and React 19.2 or later . A project on an older Node.js version must upgrade the runtime before it can run the new Next.js version.
The upgrade command is the first step. It bumps next, react, react-dom, and eslint-config-next to the latest versions and runs the codemods.
# Next.js 16.1.0 and later
pnpm next upgrade
# Earlier versions
npx @next/codemod@canary upgrade latest
# With a coding agent
npx next@canary upgrade --agent=latest
The --agent variant is documented as the path for a coding agent to perform and verify the upgrade . It is not a replacement for reviewing the diff; it is a way to automate the mechanical parts.
The codemods handle the async request APIs. The params, searchParams, cookies(), headers(), and draftMode() are Promises in the current version, and the synchronous access that was temporarily allowed in the previous major version is removed . The codemod adds the await and updates the TypeScript types. The next typegen command generates the PageProps, LayoutProps, and RouteContext helpers that make the migration type-safe .
// Before
export default function Page({ params }) {
const { slug } = params
}
// After
export default async function Page(props: PageProps<'/blog/[slug]'>) {
const { slug } = await props.params
}
The codemod does not cover every breaking change. The middleware rename to proxy.ts is manual. The parallel route default.js requirement is manual. The removal of next lint, the serverRuntimeConfig/publicRuntimeConfig options, and the AMP support are manual . The version guide documents the full list, and the upgrade is not complete until each item has been addressed.
b. The canary channel
The canary channel is installed with a package manager command that targets the canary tag. The documentation recommends being on the latest stable version and confirming that everything works before switching to canary .
pnpm add next@canary
npm i next@canary
The canary channel is not a version that a production application should track without a specific reason. The features listed as canary-only are not stable, and the breaking changes are not documented in the same way as the stable version guides. A project that adopts canary accepts the risk of an unreleased change breaking the build or the runtime.
The canary channel is appropriate for a project that needs a specific unreleased feature, or for a maintainer who is testing the next version before it is promoted. It is not appropriate for a project that simply wants the latest stable release, because next@latest already provides that.
c. The manual breaking changes
The current major version has several breaking changes that the codemod does not fix. Each requires a manual migration.
The middleware rename. middleware.ts is renamed to proxy.ts, and the exported function is renamed from middleware to proxy. The NextResponse API is unchanged. The Edge runtime is not supported in proxy.ts, so a project that depends on the Edge runtime must keep middleware.ts until the migration is complete . A deployment to Cloudflare via OpenNext must also keep middleware.ts, because OpenNext does not recognize proxy.ts .
The parallel route default. Parallel routes require a default.js file in each slot. Without it, the build fails. The file renders null when the slot has no active match .
// app/@modal/default.tsx
export default function Default() {
return null
}
The next lint removal. The next lint command is removed. The project uses ESLint directly, and the eslint-config-next package provides the configuration. The migration is to add an ESLint configuration file and run eslint . instead of next lint .
The runtime config removal. serverRuntimeConfig and publicRuntimeConfig are removed. The migration is to use environment variables, with the NEXT_PUBLIC_ prefix for the values that need to be available on the client .
The image defaults change. The next/image defaults changed: the cache TTL increased to 4 hours, the imageSizes array shrank to 5 sizes, and the qualities array defaulted to a single value (75) . A project that relied on the old defaults can revert them in next.config.ts.
Complete Example Session
# ============================================
# PART 1: THE VERSION CHECK
# ============================================
node --version
# v20.9.0 or later is required.
# ============================================
# PART 2: THE STABLE UPGRADE
# ============================================
pnpm next upgrade
# Or, for versions before 16.1.0:
npx @next/codemod@canary upgrade latest
# ============================================
# PART 3: THE MANUAL MIGRATIONS
# ============================================
# Rename middleware.ts → proxy.ts
# Rename middleware → proxy
# Keep middleware.ts if Edge runtime or OpenNext is required.
// ============================================
// PART 4: THE PARALLEL ROUTE DEFAULT
// ============================================
// app/@modal/default.tsx
export default function Default() {
return null
}
# ============================================
# PART 5: THE LINT MIGRATION
# ============================================
# Remove next lint
# Add eslint.config.mjs
npx eslint .
// ============================================
// PART 6: THE RUNTIME CONFIG MIGRATION
// ============================================
// Before: serverRuntimeConfig
// After: process.env.SECRET
// Before: publicRuntimeConfig
// After: process.env.NEXT_PUBLIC_API_URL
// ============================================
// PART 7: THE IMAGE DEFAULTS REVERT
// ============================================
// next.config.ts
const config = {
images: {
minimumCacheTTL: 60,
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
}
# ============================================
# PART 8: THE ASYNC PARAMS CODEMOD
# ============================================
npx @next/codemod@canary upgrade latest
# The codemod adds await to params, searchParams,
# cookies(), headers(), and draftMode().
# ============================================
# PART 9: THE CANARY INSTALL
# ============================================
pnpm add next@canary
# Only for a project that needs an unreleased feature.
# ============================================
# PART 10: THE BUILD VERIFICATION
# ============================================
pnpm build
# The build must succeed before the upgrade is complete.
The ten parts covered the version check, the stable upgrade, the manual migrations, the parallel route default, the lint migration, the runtime config migration, the image defaults, the async params codemod, the canary install, and the build verification.
Quick Reference
Upgrade commands
| Command | Use case |
|---|---|
pnpm next upgrade | Next.js 16.1.0 and later |
npx @next/codemod@canary upgrade latest | Earlier versions |
npx next@canary upgrade --agent=latest | Coding agent |
pnpm add next@canary | Canary channel |
Version requirements
| Requirement | Value |
|---|---|
| Node.js | 20.9 or later |
| TypeScript | 5.1 or later |
| React | 19.2 or later |
| Browsers | Chrome 111+, Safari 16.4+, Firefox 109+ |
Codemod coverage
| Migration | Codemod |
|---|---|
Async params/searchParams | Yes |
Async cookies()/headers() | Yes |
middleware → proxy | No |
Parallel route default.js | No |
next lint removal | No |
| Runtime config removal | No |
Canary features (current)
| Feature | Status |
|---|---|
forbidden | Canary |
unauthorized | Canary |
authInterrupts | Canary |
forbidden.js | Canary |
Best Practices
✅ Do This:
# Upgrade from a clean working tree
git commit -am "pre-upgrade checkpoint" # ✅
# Run the build after the upgrade
pnpm build # ✅
# Use the codemod for the async APIs
npx @next/codemod@canary upgrade latest # ✅
# Keep middleware.ts if OpenNext or Edge is required
# Rename to proxy.ts only when the deployment supports it # ✅
❌ Don’t Do This:
# Track canary in production without a reason
pnpm add next@canary # ❌
# Skip the manual migrations
# The build fails or the middleware is silently disabled # ❌
# Assume the codemod fixes everything
# The rename and the default.js are manual # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Build fails after upgrade | Parallel route default.js missing | Add the file to each slot |
| Middleware is silently disabled | Renamed to proxy.ts on OpenNext | Keep middleware.ts until OpenNext supports it |
| Async params error | Codemod not run or not complete | Run the codemod and next typegen |
| Lint command fails | next lint removed | Use eslint . |
| Runtime config undefined | serverRuntimeConfig removed | Use process.env |
| Image optimization changed | New defaults | Revert in next.config.ts |
Real-World Examples
1. Stable upgrade
pnpm next upgrade
2. Codemod for earlier versions
npx @next/codemod@canary upgrade latest
3. Async params
const { slug } = await props.params
4. Parallel route default
// app/@modal/default.tsx
export default function Default() { return null }
5. Middleware rename
# middleware.ts → proxy.ts
# middleware → proxy
6. Lint migration
npx eslint .
7. Runtime config
process.env.NEXT_PUBLIC_API_URL
8. Image defaults revert
images: { minimumCacheTTL: 60 }
9. Canary install
pnpm add next@canary
10. Build check
pnpm build
Visual
The upgrade decision tree
┌─────────────────────────────────────────────────────────────┐
│ Which channel? │
│ │ │
│ ├── Stable ──► next@latest │
│ │ └── planned migration, documented changes │
│ │ │
│ └── Canary ──► next@canary │
│ └── unreleased features, unstable │
└─────────────────────────────────────────────────────────────┘
The codemod coverage
┌─────────────────────────────────────────────────────────────┐
│ CODEMOD HANDLES MANUAL │
│ │
│ async params middleware → proxy │
│ async searchParams parallel default.js │
│ async cookies() next lint removal │
│ async headers() runtime config removal │
│ async draftMode() AMP removal │
└─────────────────────────────────────────────────────────────┘
The upgrade flow
┌─────────────────────────────────────────────────────────────┐
│ git commit (checkpoint) │
│ │ │
│ ▼ │
│ next upgrade │
│ │ │
│ ▼ │
│ manual migrations │
│ │ │
│ ▼ │
│ pnpm build │
│ │ │
│ ├── success ──► deploy │
│ │ │
│ └── failure ──► fix and rebuild │
└─────────────────────────────────────────────────────────────┘
The canary risk
┌─────────────────────────────────────────────────────────────┐
│ Canary: every commit, not every feature is stable. │
│ Stable: tested, documented, supported. │
│ │
│ Production should track stable unless there is a reason. │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Stable upgrade command | pnpm next upgrade (16.1.0+) |
| Earlier upgrade command | npx @next/codemod@canary upgrade latest |
| Canary install | pnpm add next@canary |
| Node.js requirement | 20.9+ |
| React requirement | 19.2+ |
| Codemod coverage | Async request APIs |
| Manual migrations | Middleware rename, parallel default, lint, runtime config |
| Build verification | pnpm build |
Key takeaways:
- The stable channel is the default for production.
next@latestresolves to the stable release, and the version guides document the breaking changes. The canary channel is for unreleased features and carries the risk of undocumented changes . - The upgrade command automates the version bump and the codemods.
pnpm next upgradeis available from 16.1.0. Earlier versions use the@next/codemodpackage. The codemod handles the async request APIs but not the structural migrations . - The middleware rename is manual and has deployment implications.
middleware.tsbecomesproxy.ts, and the exported function becomesproxy. The Edge runtime is not supported, and OpenNext does not recognize the new filename. A project on Cloudflare must keepmiddleware.tsuntil the support is added . - Parallel routes require a
default.jsfile. Without it, the build fails. The file rendersnullwhen the slot has no active match . next lintis removed. The migration is to use ESLint directly with theeslint-config-nextpackage. Thenext lintcommand no longer exists .- The image defaults changed. The cache TTL, the
imageSizesarray, and thequalitiesarray are different from the previous version. A project that relied on the old values can revert them innext.config.ts.
Remember: Upgrading Next.js is a planned migration, not a package bump. Start with the version check, run the upgrade command, and then work through the manual changes. The codemod handles the async APIs; the middleware rename, the parallel default file, and the lint migration are manual. Verify with a build before deploying. And keep the canary channel for the cases that need an unreleased feature, not for the projects that simply want the latest release.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!