Aller au contenu principal
All articles
Engineering

Next.js 15 in production: what changes with React 19

Server Components by default, partial prerendering, new caching API: field notes from migrating a Next.js 14 app to 15.

VALRY LABS Engineering TeamJanuary 14, 20259 min read

Server Components: the norm, no longer the exception

Next.js 15 buries the debate: a component is a server component by default. The `"use client"` marker becomes the documented exception, and that is a clear win for both performance and maintainability.

In practice: 80% of a typical marketing app can stay in RSC. Client islands are limited to components that handle state, effects, events, browser APIs, or Framer Motion. On the VALRY LABS codebase, that comes to less than 15% of components.

The classic trap: passing non-serialisable data (Date, Map, functions) between a Server and a Client Component. The strict rule — only pass JSON-serialisable types or Server Actions — avoids surprises.

React 19: actions, useActionState, useFormStatus

React 19's Actions API unifies mutations and form submissions. A Server Action becomes a typed async function you can wire directly onto `<form action={myAction}>`. No more `onSubmit` + client-side `fetch`.

`useFormStatus` inside the submit button exposes `pending` without prop drilling. `useActionState` separates success/error state from the result. A typical form drops from 60 lines to 25.

Watch out for the UX trap: until the action completes, the user can click again. Disable the button via `pending`, and handle idempotency on the server (a unique key per submission).

Partial Prerendering (PPR): the SSR+SSG holy grail

PPR merges the best of static and dynamic: a page can be partially prerendered (static shell) with dynamic chunks streamed in afterwards. The LCP is served immediately from the CDN; user data arrives later.

Concretely: mark your layouts/pages with `experimental_ppr = true`, and Next.js statically prerenders everything it can, leaving holes (`<Suspense>`) for the dynamic parts.

In production on VALRY LABS: the home page drops to a 0.6 s LCP (from 1.1 s) without sacrificing dynamic data (stats, testimonials, latest articles). It is the highest-impact feature of 15.

Caching: the great clarification

Next.js 15 removed default caching on `fetch`. It is the healthiest framework decision in three years: too many teams got bitten by stale data in development.

In exchange, you must be explicit: `fetch(url, { cache: 'force-cache' })` for ISR, `next: { revalidate: 3600 }` for a TTL, or `cache: 'no-store'` for strictly dynamic.

Our convention: every marketing route uses `export const revalidate = 3600` at the page level, and every user-auth route leans on dynamic rendering by default. No surprises.

Migrating 14 → 15: the checklist

1. Update `next`, `react`, `react-dom` to 15.x / 19.x. Run the official codemod: `npx @next/codemod@latest upgrade`.

2. Check usages of `cookies()`, `headers()`, `params` — they are now async (they return a Promise). The codemod wraps them in `await` automatically.

3. Audit `runtime = 'edge'` routes: some can stay on the Node.js runtime, which is more flexible. Edge remains relevant for middleware and geo-distributed routes.

4. Run the build in CI, fix the typing errors (React 19 tightens several types), and compare First Load JS before/after — 15 often shrinks the bundle by 5–10%.

5. Roll out progressively (flags) to a slice of user traffic, and watch Sentry and CWV for 7 days.

Key takeaways

Key points.

  • Server Components by default: 80% of a marketing app stays on the server, less client bundle.
  • PPR merges static and dynamic — LCP halved on VALRY LABS (1.1 s → 0.6 s).
  • Caching: default `fetch` caching is gone, be explicit on every call.
  • Migrate 14 → 15 via the official codemod + 7 days of Sentry/CWV monitoring.
Next.jsReact 19RSCPerformance