Upgrading to Next.js 15 is one line:
- "next": "14.2.15" + "next": "15.0.3" The work is in the files that line affects. Three changes cause most of it.
1. Request APIs are async
cookies(), headers() and draftMode() now return promises.
import { cookies } from "next/headers"; export default async function Page() { - const theme = cookies().get("theme"); + const theme = (await cookies()).get("theme"); ... } Next.js keeps a temporary synchronous fallback that logs a warning, which is kind for the upgrade and unkind for finding the problem: the code keeps working, and the warning sits in your logs.
In route params, the same applies: params and searchParams in pages and layouts are promises too.
2. fetch() isn't cached by default
In 14, fetch() in a server component was cached unless you opted out. In 15 it isn't cached unless you opt in:
await fetch(url, { cache: "force-cache" }); Nothing breaks. Some pages that were fast because of a cache you never meant to rely on get slower.
3. GET route handlers aren't cached
Same idea for app/**/route.ts: a GET handler is dynamic by default now. Add export const dynamic = "force-static" where you really wanted it cached.
Finding every affected file
The official codemod handles most of change 1:
npx @next/codemod@canary upgrade latest Then search for what's left, because codemods miss dynamic patterns and helper functions:
grep -rnE "cookies\(\)|headers\(\)|draftMode\(\)" app src grep -rn "fetch(" app src | grep -v "cache:" And run your build and tests before you call it done. next build catches type errors from the async change if your project is typed.
This "the release notes say what changed, but not where it hits your code" step is the part Versyka automates: it reads the release notes against your repository, rewrites the affected calls and runs your checks before and after.
Have you moved to Next.js 15 yet? What broke first?
