My Experience Navigating the Next.js 15 Upgrade

Next.js 15 was released earlier today and I’m thrilled to share my recent journey upgrading a key project from Next.js 14 to Next.js 15. This adventure was undertaken to test out the latest features and understand the upgrade experience from a firsthand perspective—essentially stepping into the shoes of the broader user community.

Why Upgrade to Next.js 15?

The motivation to upgrade wasn’t driven by a pressing need but rather by an eagerness to explore new enhancements. Key features like the improved Turbopack and the default no-cache option particularly caught my eye. My goal was to see how seamless (or challenging) the upgrade process would be for real-world applications.

Setting the Expectations and Preparing for the Journey

Expectations:

  • Seamless Codegen: I hoped the codegen tool would manage most of the heavy lifting.

  • Manual Interventions: Knowing the complexity of my monorepo, I prepared myself for some manual fixes.

Preparations:

Starting the Upgrade Process

With a mix of excitement and trepidation, I initiated the upgrade using the npx @next/codemod@canary upgrade latest command. Initially, the codegen tool performed admirably, handling a bulk of the transformations effortlessly. But as the build process continued, multiple failures emerged, and my optimism began to wane.

Facing Codegen Challenges

Despite its initial success, the codegen tool encountered hitches with certain synchronous to asynchronous API transitions, necessitating manual tweaks.

Layout Props: The automated codemod didn’t fully address synchronous layout props transitioning to asynchronous.

Before:

export default async function Layout(props: PropsWithChildren<{ params: { path: string | string[] } }>) {
  const { path } = params;
  const { children } = props;
}

After:

export default async function Layout(props: PropsWithChildren<{ params: Promise<{ path: string | string[] }> }>) {
  const params = await props.params;
  const { path } = params;
  const { children } = props;
}

Cookies Function: Transitioning from synchronous to asynchronous cookies functions was another hurdle.

Before:

const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null;

After:

const sessionId = (await cookies()).get(lucia.sessionCookieName)?.value ?? null;

Tackling React 19 Compatibility

Upgrading to React 19 introduced its own set of challenges, with significant changes in ReactNode and other internals.

Framer Motion:
  • Issue: Incompatibility with React 19.

  • Solution: Upgraded to framer-motion@12.0.0-alpha.1.

  • Details: Check out GitHub Issue #2668.

React Email:
  • Issue: Outdated @react-email/components causing type errors.

  • Solution: Upgraded using the command pnpm up @react-email/components@latest.

  • Details: See GitHub Issue #1413.

Content Collection:
  • Issue: Broken types due to non-matching peer dependencies.

  • Solution: Temporarily used any to bypass type errors while waiting for it to be patched for React 19.

useRef Hook:
  • Change: Explicit initialization needed.

  • Before:

    const ref = useRef<number>(null);
  • After:

    const ref = useRef<number | null>(null);

Common Errors and Their Resolutions

Error: TypeError: Cannot Read Properties of Undefined (ReactCurrentDispatcher)

Grappling with Turbopack Limitations and Monorepo Challenges

Turbopack, while powerful, currently doesn’t support externals like Webpack. This necessitated removing --turbo from the build commands and reverting to Webpack.

Handling the monorepo configuration posed its own set of challenges. Ensuring compatibility across numerous sub-repositories, combined with legacy boilerplate code, certainly tested my patience.

Strategy: Moved the pnpm override to the root directory to maintain consistency across the entire monorepo.

Recommendations for Smoother Upgrades

To navigate the upgrade terrain more efficiently, here are a few steps I found helpful:

Incremental Upgrades:

  • Tackle one component at a time to better isolate and address issues.

Manual Downgrade:

  • If overwhelmed by issues, upgrade Next.js to 15 first while initially downgrading react and react-dom to version 18.x.x. Once stable, proceed with upgrading React.

Conclusion

Upgrading to Next.js 15, especially alongside React 19, is indeed a complex venture filled with numerous challenges. However, through careful planning, utilizing community resources, and adopting an incremental upgrade approach, the process is manageable. Staying in tune with the latest from Next.js and React will further ensure smoother transitions in the future.

For more detailed insights, refer to the official Next.js 15 Upgrade Guide.

Raymond Yeh

Raymond Yeh

Published on 22 October 2024

Get engineers' time back from marketing!

Don't let managing a blog on your site get in the way of your core product.

Wisp empowers your marketing team to create and manage content on your website without consuming more engineering hours.

Get started in few lines of codes.

Choosing a CMS
Related Posts
Upgrading to Next.js 15  in 5 Mins using Codemod

Upgrading to Next.js 15 in 5 Mins using Codemod

Upgrade your Next.js project in record time! Discover my step-by-step journey upgrading to Next.js 15,using the official codemode tool. Watch the video too!

Read Full Story
Next.js 15 is out! What's new and what broke?

Next.js 15 is out! What's new and what broke?

Next.js 15: A Must-Read for Web Developers! Learn about the latest updates, new observability APIs, and improved TypeScript support. Equip yourself with the knowledge to upgrade effortlessly!

Read Full Story
When to Say No to Next.js: The Guide for Minimalist Web Developers

When to Say No to Next.js: The Guide for Minimalist Web Developers

The appeal of Next.js is obvious - built-in server-side rendering, static site generation, file-based routing, and API routes promise a smooth, full-stack development experience. But at what cost?

Read Full Story