← Back to BlogNext.js

Next.js 15: Everything You Need to Know

Hammad Nadir May 28, 2025 8 min read
Next.js 15: Everything You Need to Know

Explore the game-changing features of Next.js 15 including the new caching system, Partial Prerendering, and enhanced Server Components.

Next.js 15 is a smaller release than the App Router transition, but the changes it makes are unusually consequential because they alter defaults. Defaults are what most applications actually run on, and a changed default behaves like a silent rewrite of code you did not touch.

Here is what changed, why it changed, and what it means for an existing codebase.

Caching Is No Longer Aggressive by Default

This is the headline change and the one most likely to alter your application's behavior without any code change on your part.

Earlier App Router versions cached almost everything by default. Fetch requests were cached indefinitely unless told otherwise. GET route handlers were cached. Client-side navigation reused cached router state for a fixed period. The intent was performance by default, and the effect, for a very large number of teams, was stale data and a confusing debugging experience.

The mental model most developers arrived at was that data was cached in ways they could not predict and did not ask for.

Next.js 15 reverses the defaults. Fetch requests are uncached unless you opt in. GET route handlers are uncached. Client router caching for page segments no longer holds by default.

The practical consequence for an upgrade is a load increase on your data sources. An application that was inadvertently serving thousands of requests from cache may start hitting the database for every one of them. This is not a regression; it is the framework doing what your code actually said. But it will show up as a spike, so upgrade with your database metrics visible.

The correction is to opt back in deliberately. Mark the fetches that genuinely tolerate staleness, choose a revalidation window for each, and leave the rest live. The result is a caching strategy someone can read and reason about, which the previous default made nearly impossible.

Async Request APIs

Request-scoped APIs — cookies, headers, params, and searchParams — are now asynchronous and must be awaited.

The reasoning is architectural. As long as these were synchronous, any component reading them forced the framework to wait for the incoming request before it could render anything at all. Making them async lets Next.js begin rendering everything that does not depend on the request and suspend only the parts that do. That is the mechanism that makes Partial Prerendering possible.

This is a breaking change with a broad blast radius, since these APIs appear throughout most applications. The official codemod handles the mechanical conversion well. Budget a code review pass afterward for the cases it cannot infer, particularly helpers that read cookies deep inside a synchronous call chain — those need the async signature threaded upward, and that is where the work actually is.

Partial Prerendering

Partial Prerendering is the most architecturally interesting feature in the release, and it resolves a tradeoff that has shaped page-level decisions for years.

Until now, a route was static or dynamic. Static routes are fast, cacheable at the edge, and cannot show anything user-specific. Dynamic routes can show anything and pay a server round trip for the entire page. A marketing page with a personalized greeting in the header had to become fully dynamic because of that one element.

Partial Prerendering removes the page-level choice. The static shell is generated at build time and served instantly from the edge. Dynamic parts are wrapped in Suspense boundaries, rendered on demand, and streamed into the shell as they resolve.

The user sees the layout, the copy, the images, and the navigation immediately, with the personalized fragments filling in. One request, one response, no client-side waterfall.

The design implication is worth internalizing: your Suspense boundaries are now a performance architecture, not just a loading-state convenience. Where you draw them determines what is instant and what waits. Draw them tightly around the genuinely dynamic pieces, and give each one a fallback that occupies the same space as the real content so the layout does not shift.

Turbopack for Development

Turbopack is stable for development, and on medium and large codebases the difference is not subtle. Cold starts and hot updates both drop substantially, and hot updates are the number that actually affects your day, because you experience it hundreds of times.

Production builds still run through webpack in this release. That split is temporary but worth knowing, because it means your development and production builds do not run identical toolchains. Resolution and transform edge cases can differ. Do not treat a passing dev server as proof that the production build works — that has always been true and is more true now.

React 19 Underneath

Next.js 15 ships on React 19, which brings its own set of changes.

The form Actions API lets a form submit directly to a server function without a manual handler, with pending state available through a hook. For the very common case of a form that posts data and shows a spinner, this removes a real amount of boilerplate.

The optimistic update hook standardizes the pattern of showing a change immediately and reconciling when the server responds. Most teams had written some version of this by hand; having one implementation with correct rollback behavior is a genuine improvement.

Ref handling is simplified — refs pass as ordinary props to function components, and the forwarding wrapper is no longer required for new code.

What an Upgrade Actually Looks Like

Run the official codemod first. It handles the async request APIs and the majority of the mechanical churn, and doing it by hand is a waste of a day.

Then audit your data fetching explicitly. This is the step teams skip and regret. Walk the list of every fetch in the application and make a deliberate decision about each one: does it tolerate staleness, and if so for how long. The old defaults were making that decision silently and often wrongly. Making it explicitly is the actual value of the upgrade.

Watch your database load on the first deploy. The caching change moves traffic from cache to origin, and if you were unknowingly depending on the old behavior, you want to find that out with a dashboard open rather than through a support ticket.

Then add Partial Prerendering where it pays. Look for routes that are mostly static but were forced dynamic by one small personalized element — dashboards with a static shell, product pages with live inventory, any page with a logged-in header over public content. Those are where the improvement is largest.

Is It Worth Upgrading

For a new project, start here without hesitation. The defaults are more predictable, and predictable defaults are worth a great deal on a codebase that will be maintained by people who did not write it.

For an existing App Router application, the upgrade is real work but bounded, and the caching change alone justifies it. The previous defaults produced a category of bug that was genuinely hard to diagnose — data that was correct in development, stale in production, and cached by a layer nobody had configured. Removing that class of problem is worth a week.

For an application still on the Pages Router and working well, there is no urgency. Pages Router remains supported. Migrate when you have a reason beyond version numbers.

Next.js React Performance SSR