A Next.js upgrade changes more than a number in a package file. Version 16 makes request APIs fully asynchronous, uses Turbopack by default, and removes the old next lint command. A careful Next.js 16 migration begins with an inventory and ends with real route checks. This guide explains the work in plain steps so a team can plan the change without assuming every project needs the same fix.

What changes in a Next.js 16 migration?

A Next.js 16 migration changes framework packages, request API usage, build behavior, and parts of the development workflow. The official version 16 upgrade guide is the source to check against your exact starting version. Read it before applying a code change copied from an older article.

The minimum Node.js version for Next.js 16 is 20.9, and TypeScript 5.1 or newer is required. React and React DOM must be moved to compatible versions together. If your application starts from Next.js 14, you also need to account for changes introduced in version 15. Do not jump over those notes simply because your package manager accepts the install command.

Request values such as params, searchParams, cookies(), and headers() are asynchronous. Next.js 15 allowed a temporary synchronous style in some places. Version 16 removes that compatibility. A page that reads params.slug directly may need to await params first.

Turbopack is the default for development and production builds. A project with custom Webpack behavior needs an explicit review. The next lint command is gone, so linting must call a tool such as ESLint directly. These are practical changes to scripts and tests, not just internal implementation details.

How do you prepare before changing packages?

List the routes, dependencies, custom configuration, and existing failures before upgrading. This gives you a baseline. Without it, a broken page discovered after the upgrade may be a new problem or one that existed already.

Run the current build, type check, and lint commands. Save the result. Open the key routes in a browser, including dynamic product or blog pages, forms, and error states. Record the expected behavior for each. Check a phone width and a desktop width. If you have screenshots, use them to spot layout changes later.

Read the package file and lockfile. Note any libraries that declare support only for an older React major version. Animation, editor, authentication, and chart libraries can fail in ways that are not obvious at install time. Remove unused dependencies before the upgrade if you can verify they are truly unused. That makes conflicts easier to understand.

Check server infrastructure too. Confirm the Node.js version locally and where the app will eventually run. Next.js 16 cannot run on Node.js 18. If a CI workflow, Docker image, or hosting setting still uses the old runtime, update that part of the plan before treating a local success as complete.

  • Record the current branch and uncommitted work.
  • Run build, type check, lint, and existing tests.
  • List routes and their expected outcomes.
  • List custom Webpack, middleware, or image settings.
  • Check Node.js and TypeScript versions in every environment.

How should you upgrade dependencies?

Upgrade Next.js, React, React DOM, and React types as one compatible set. Use the package manager already used by the project and commit the lockfile change with the source changes. The Next.js upgrade guide offers a codemod and manual steps; choose one route and inspect its diff.

Do not blindly force peer dependency conflicts. A peer warning tells you another package may not support the chosen React version. Look up that package's official compatibility notes. Update it, replace it, or remove it if unused. A successful install with --force is not evidence that the app works.

If you also update Tailwind CSS, treat that as a separate visible change within the same project plan. Tailwind 4 uses a different PostCSS plugin and CSS entry point. The official upgrade guide explains migration details. Test a page with many custom design tokens, because a compiling stylesheet can still lose colors or spacing when configuration is not loaded.

After installation, read the package diff. Make sure you did not accidentally add an old package version through a transitive dependency or remove a library that a route still imports. A small clean dependency list reduces future security and maintenance work.

Check the React Server Components security guide when your dependency inventory includes server component packages.

Where do async request APIs need edits?

Search page, layout, route, and metadata functions for synchronous request values. In Next.js 16, dynamic params and page searchParams should be awaited before use. The same rule applies to cookies(), headers(), and draftMode() where they are used.

For a dynamic page, a type can describe params as a promise. Then the page can call const { slug } = await params. Metadata generation needs the same treatment. Search for params.slug in every route rather than fixing only the first error shown by the compiler.

Be careful with client components. A server page can await request values and pass plain strings to its client child. Moving all request work into the client may add loading states and expose data that belongs on the server. Keep each value on the side that needs it.

Cookies and headers are request specific. Avoid reading them in a module at import time. Await them inside the request function that uses them. This also makes the code easier to test and prevents surprising behavior when a build or cache process imports the module.

What about existing Pages Router routes?

Pages Router routes need their own review, even if the App Router receives most of the breaking API changes. A mixed project may have both systems. Confirm that old routes still resolve and that navigation does not point to duplicate content. Migrate only when it improves the project; the version upgrade and router migration need not be the same task.

What should happen to linting?

Replace next lint with a direct lint command and run it separately from the build. Next.js 16 removes the old command, and the build no longer runs lint automatically. If your project previously relied on the build to catch lint problems, add a separate step to local scripts and CI.

ESLint's flat configuration is now the expected direction for the Next.js plugin. Move rules carefully. A new configuration can produce many warnings unrelated to the framework upgrade. Decide which are real code problems and which reflect an obsolete rule. Do not silence every warning at once.

Keep a type check as another separate step. TypeScript can catch mismatched params types and stale component props before a browser test. A useful release gate is simple: install from the lockfile, run lint, run type check, build, then test important routes.

If the project has no meaningful tests, create targeted checks for risky behavior. Checkout gating, signed webhooks, and redirects deserve tests because failure can affect money or access. A reversible color change may only need visual inspection.

How does Turbopack affect the build?

Turbopack becomes the default, so verify any custom bundler behavior that your project depends on. Many projects can use the default without changing code. A project with a custom Webpack configuration may fail a Turbopack build or produce different assets.

Read next.config before the first new build. Check loaders, aliases, image handling, and any plugin that quietly adds a Webpack option. The official guide describes using --webpack as an explicit fallback while custom behavior is migrated. A fallback can keep work moving, but record it so the team knows the build path.

Run both development and production builds. Development mode can hide a route that only fails during static generation. Production mode can expose a missing environment variable or server-only import that was not exercised locally. Test the same dynamic routes after the build starts.

Do not compare build times based on one run. A cold cache and a warm cache tell different stories. The main acceptance test is correctness: routes render, assets load, styles apply, and server functions behave as expected.

How should you verify routes and metadata?

Open every route that customers or search engines use, including redirects and error pages. A Next.js 16 migration can compile successfully while a dynamic page still fails when opened with a real slug. Test pages with expected content and missing slugs.

Check generated metadata, canonical links, and sitemap entries. A framework migration is a good time to remove duplicate or stale URLs. If an older article has a replacement, use a permanent redirect and link to the current article internally. Ensure a planned product page is not indexed as a purchasable offer.

Use the browser's network view for images, scripts, and styles. Broken assets can look like a design regression. Inspect mobile navigation and forms with a keyboard. Turn on reduced motion and confirm the site does not force heavy animation. These checks catch failures a type checker cannot.

For server rendered counters or important copy, inspect the initial HTML with JavaScript disabled. A number that appears after hydration may be invisible to some users or crawlers if its initial output is zero. Do not rewrite a component based on a report alone; test the actual output first.

For a page-by-page review after the upgrade, use the website SEO audit checklist.

What should you do with environment variables?

Keep secrets on the server and document each required variable with a safe example. An upgrade can change when code runs or how a module is bundled. Review imports that touch payment keys, database URLs, and webhook secrets.

Never place a secret in a variable intended for the browser. In Next.js, public environment variables use the NEXT_PUBLIC_ prefix and can be included in client code. A checkout API key and webhook secret must stay server side. A public analytics measurement ID is different from a secret, but analytics should still load only after consent where required.

Create a sample environment file that lists names and explains their purpose without real values. Use a separate test store for payment development. The production checkout switch should require real seller details, final legal review, ready product files, and successful provider tests. The upgrade itself should not open sales.

Check how the app behaves when optional variables are absent. A local developer should see a safe disabled state, not an accidental live request or a misleading purchase button. Clear failure behavior is part of a safe migration.

How do you handle a failed upgrade?

Reduce a failed upgrade to one reproducible error before changing more code. Read the first meaningful build or runtime message. A wall of later errors may come from one bad import or configuration file. Keep your baseline and diff nearby.

Separate dependency failures from application failures. If installation fails, inspect package compatibility. If types fail, fix API shapes. If styling fails, inspect the Tailwind entry point and custom tokens. If a route returns an error, open it directly and read its server log. Treat each category in order.

Do not discard unrelated local work to get a clean branch. If the project already had changes, keep them visible and preserve them. A separate commit or worktree can help, but only when it fits the team's workflow. Document any known limitation you leave behind.

After a fix, rerun the narrow check that failed. Then run the full set once when all issues are addressed. Repeating the whole build after every tiny edit costs time and can hide the direct cause.

What is a sensible release gate?

A sensible release gate proves the upgraded site works for its important users and business actions. It does not require a perfect score on every tool. Define acceptance checks before you mark the Next.js 16 migration complete.

At minimum, install from the lockfile, run lint and types, build, and open major routes. Test mobile and desktop layouts, keyboard navigation, redirects, sitemap output, and consent behavior. If the site handles payments, test invalid webhook signatures, duplicate notifications, refunds, and subscription changes in the provider's test mode.

Record the exact versions that passed. Keep a short note about any fallback such as a Webpack build flag. This makes the next upgrade easier for another developer to understand.

The official Next.js guide remains the source for version-specific instructions. Use this article as an execution plan, and verify every step against the release you install.

Frequently asked questions

Can I jump from Next.js 14 directly to 16?

Yes, but review both intermediate and version 16 changes. A package install can succeed while an older request API or dependency still fails.

Is Turbopack required for every project?

It is the default in version 16. A project can use the documented Webpack fallback while custom behavior is migrated.

Does the build still run lint automatically?

No. Run ESLint separately and add it to your release checks.

Should I deploy immediately after a successful build?

No. Open real routes, test user tasks, and check metadata and integrations first.