# Next.js 15 React 19 Changes: What Actually Changed for Production Apps
Summary: Next.js 15 and React 19 represent a fundamental paradigm reset for enterprise web engineering. Key updates include asynchronous server request APIs (cookies(), headers(), dynamic routing parameters), opt-in caching replacing aggressive default memoization, native React 19 Actions (useActionState, useOptimistic), deprecation of forwardRef in favor of standard ref props, and experimental React Compiler integration. These architectural shifts eliminate silent hydration drift and establish predictable, streaming-first edge and Node.js production deployments.
---
The Architectural Shift: Moving Away from Implicit Magic
The transition from Next.js 14 to Next.js 15, paired with the formal release of React 19, marks a decisive departure from the hyper-aggressive caching heuristics that characterized the early App Router era. When Next.js 13 and 14 introduced Server Components to the broader ecosystem, the framework attempted to compensate for dynamic overhead by defaulting to aggressive memoization: fetch requests were cached indefinitely (force-cache) by default, GET Route Handlers were pre-computed statically unless dynamic headers were detected, and client-side page transitions reused dynamic route segments via the client router cache.
While this approach produced impressive Lighthouse metrics for greenfield demos, it created severe friction in enterprise production environments. Software architects encountered phantom cache invalidation bugs, memory bloat on containerized Node.js clusters, and critical security hazards where multi-tenant data bled across unkeyed cache entries. Teams were forced to litter their codebases with export const dynamic = 'force-dynamic' and revalidatePath calls simply to guarantee that customers received fresh data.
Next.js 15 and React 19 dismantle this implicit magic. In their place stands an explicit, standards-aligned execution model. Dynamic server data is uncached by default, request parameters are handled asynchronously to support streaming concurrency, and React 19 formalizes primitives like Server Actions and Asset Preloading directly into the core library runtime. For tech leads and platform architects, this upgrade requires systematic refactoring of existing contracts, but it delivers deterministic execution, predictable memory footprints, and uncompromised rendering performance.
---
Breaking Runtime Changes: Asynchronous Request APIs
The most widespread code modification required when adopting Next.js 15 is the conversion of synchronous request data access to asynchronous contracts. In Next.js 14, accessing headers, cookies, or route parameters was synchronous. In Next.js 15, these APIs return Promises that must be resolved before consumption.
The Mechanical Need for Asynchronous Request Context
To understand why this breaking change was implemented, consider how modern rendering engines execute Partial Prerendering (PPR). Under PPR, the server prerenders an immutable static HTML shell at build time or during edge deployment, leaving dynamic holes to be populated via streaming when an incoming HTTP request arrives.
When a layout or component accessed cookies() or params.id synchronously under Next.js 14, the runtime was forced to halt execution immediately or bail out of static shell compilation for that entire sub-tree. By transforming request properties into Promises, Next.js 15 decouples component instantiation from request resolution. The framework can execute the static tree up to the point of promise evaluation, stream the static shell immediately to the client, and resolve the request promises concurrently as headers and connection streams become available.
Migrating Server Components, Layouts, and Route Handlers
The APIs affected by this change include:
cookies()fromnext/headersheaders()fromnext/headersparamspassed topage.tsx,layout.tsx, androute.tssearchParamspassed topage.tsx
The following TypeScript example demonstrates the mandatory migration pattern in a Server Component:
// Next.js 14: Synchronous access (Deprecated)
// app/organizations/[orgId]/settings/page.tsx
interface PagePropsLegacy {
params: { orgId: string };
searchParams: { tab?: string };
}
export default function SettingsPageLegacy({ params, searchParams }: PagePropsLegacy) {
// Synchronous access in Next.js 14 triggered runtime warnings in Next.js 15
const activeOrg = params.orgId;
const currentTab = searchParams.tab ?? 'general';
return (
<main>
<h1>Org: {activeOrg}</h1>
<p>Active Tab: {currentTab}</p>
</main>
);
}
// Next.js 15: Asynchronous access (Production Standard)
// app/organizations/[orgId]/settings/page.tsx
import { cookies, headers } from 'next/headers';
interface PagePropsModern {
params: Promise<{ orgId: string }>;
searchParams: Promise<{ tab?: string }>;
}
export default async function SettingsPageModern({
params,
searchParams,
}: PagePropsModern) {
// Concurrently resolve parameters and request headers
const [{ orgId }, query, cookieStore, headerList] = await Promise.all([
params,
searchParams,
cookies(),
headers(),
]);
const sessionToken = cookieStore.get('session_id')?.value;
const clientIp = headerList.get('x-forwarded-for') ?? '127.0.0.1';
const currentTab = query.tab ?? 'general';
return (
<main className="p-8">
<h1>Organization: {orgId}</h1>
<p>Tab: {currentTab}</p>
<p>Client IP: {clientIp}</p>
<span className="text-sm text-slate-500">Session Verified: {Boolean(sessionToken)}</span>
</main>
);
}In Route Handlers (app/api/v1/telemetry/[eventId]/route.ts), the second parameter containing route context now requires awaiting params:
// app/api/v1/telemetry/[eventId]/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface RouteContext {
params: Promise<{ eventId: string }>;
}
export async function GET(
request: NextRequest,
context: RouteContext
): Promise<NextResponse> {
const { eventId } = await context.params;
if (!eventId) {
return NextResponse.json({ error: 'Missing event identifier' }, { status: 400 });
}
return NextResponse.json({
status: 'acknowledged',
eventId,
timestamp: new Date().toISOString(),
});
}Unwrapping Dynamic Parameters in Client Components via use()
When dynamic parameters must be read inside Client Components ('use client'), passing resolved props down through intermediate boundaries creates unnecessary prop drilling. React 19 introduces the use() hook, which can unwrap Promises and Context directly inside the client component render phase:
'use client';
// app/organizations/[orgId]/components/ClientHeader.tsx
import React, { use } from 'react';
interface ClientHeaderProps {
paramsPromise: Promise<{ orgId: string }>;
}
export function ClientHeader({ paramsPromise }: ClientHeaderProps) {
// React 19 use() suspends until paramsPromise resolves
const { orgId } = use(paramsPromise);
return (
<header className="border-b py-4">
<h2 className="text-xl font-bold">Context: {orgId}</h2>
</header>
);
}---
The Caching Overhaul: The End of Aggressive Defaults
In Next.js 14, fetch() requests were cached globally unless explicitly configured otherwise. If an engineer omitted the cache option on a query calling an internal microservice, Next.js intercepted the native fetch and committed the result to its local Data Cache.
The Transition from force-cache to no-store
Next.js 15 realigns with the native web standard:
1. `fetch` Default Changed to `no-store`: Unconfigured fetch calls execute fresh HTTP requests on every server invocation.
2. `GET` Route Handlers Default to Dynamic: Handlers using the GET method are no longer statically cached at build time unless explicitly configured via route segment flags.
3. Client Router Cache Stale Times: Client navigations between dynamic pages no longer hold on to dynamic page caches for 30 seconds. Instead, the default dynamic stale time is set to 0 seconds, guaranteeing that route transitions reflect real-time database state.
// Next.js 14: This was cached indefinitely by default!
const data = await fetch('https://api.internal/metrics');
// Next.js 15: Equivalent to native fetch (fresh on every render)
const data = await fetch('https://api.internal/metrics');
// cache default is now 'no-store'Restoring Deterministic Static Caching
For enterprise platforms operating high-traffic catalog pages or read-heavy marketing content, hitting backend services on every invocation can overwhelm database connection pools. To re-enable caching in Next.js 15, you must opt in explicitly at either the fetch level or the route segment level:
// Explicit static caching with tag-based on-demand revalidation
export async function getProductCatalog(tenantId: string) {
const response = await fetch(`https://inventory.internal/v1/${tenantId}/items`, {
cache: 'force-cache', // Explicit opt-in
next: {
tags: [`catalog_${tenantId}`],
revalidate: 3600, // Stale-while-revalidate for 1 hour
},
});
if (!response.ok) {
throw new Error(`Failed to fetch catalog: ${response.statusText}`);
}
return response.json();
}For GET Route Handlers intended to serve static JSON feeds, you must declare the segment configuration at the file boundary:
// app/api/static-navigation/route.ts
export const dynamic = 'force-static';
export const revalidate = 86400; // Cache for 24 hours
export async function GET() {
const navigationTree = await fetch('https://cms.internal/navigation', {
cache: 'force-cache',
}).then(res => res.json());
return Response.json(navigationTree);
}Client Router Navigation Cache Controls
To control how the browser client cache retains layout and page data during client-side navigation transitions, Next.js 15 exposes fine-grained configuration in next.config.ts via experimental.staleTimes:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
experimental: {
staleTimes: {
dynamic: 0, // 0 seconds: always revalidate dynamic segments on navigation
static: 300, // 5 minutes: retain static segments across route transitions
},
},
};
export default nextConfig;This configuration prevents the dreaded stale-state bugs common in Next.js 14 where an admin updated a record, navigated to a list view via <Link>, and saw the old record until triggering a full browser refresh.
---
React 19 Core Engine Upgrades in Production
Next.js 15 is built upon React 19. Rather than relying on custom framework abstractions, React 19 incorporates state management, mutation lifecycles, and asset preloading directly into the core React runtime. Understanding these react 19 features is mandatory for clean enterprise architecture.
Streamlined Mutations with useActionState and useOptimistic
Prior to React 19, managing form submissions and pending states required bespoke combinations of useState, useEffect, and custom event handlers. React 19 replaces the deprecated useFormState hook with useActionState, providing a clean state machine for handling asynchronous mutations, error handling, and pending UI transitions.
When paired with useOptimistic, client applications can provide immediate feedback to user interactions without waiting for server network round-trips.
'use client';
// components/WorkspaceRenameForm.tsx
import React, { useActionState, useOptimistic } from 'react';
interface WorkspaceState {
name: string;
error: string | null;
}
// Server Action definition imported from server boundary
async function updateWorkspaceAction(
prevState: WorkspaceState,
formData: FormData
): Promise<WorkspaceState> {
const newName = formData.get('workspaceName')?.toString().trim();
if (!newName || newName.length < 3) {
return { name: prevState.name, error: 'Name must contain at least 3 characters.' };
}
try {
const res = await fetch('/api/workspaces/rename', {
method: 'POST',
body: JSON.stringify({ name: newName }),
headers: { 'Content-Type': 'application/json' },
});
if (!res.ok) throw new Error('Network mutation rejected');
return { name: newName, error: null };
} catch (err) {
return { name: prevState.name, error: 'Failed to update workspace name. Please retry.' };
}
}
export function WorkspaceRenameForm({ initialName }: { initialName: string }) {
const [state, formAction, isPending] = useActionState(updateWorkspaceAction, {
name: initialName,
error: null,
});
// Optimistic UI representation
const [optimisticName, setOptimisticName] = useOptimistic(
state.name,
(_currentName, appliedName: string) => appliedName
);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const targetName = formData.get('workspaceName')?.toString() ?? '';
// Instantly reflect state on client
setOptimisticName(targetName);
// Dispatch asynchronous Server Action
React.startTransition(() => {
formAction(formData);
});
};
return (
<div className="rounded-lg border p-6 max-w-md">
<div className="mb-4">
<span className="text-xs uppercase text-slate-400 font-semibold tracking-wider">
Current Workspace
</span>
<h3 className="text-lg font-bold text-slate-800">{optimisticName}</h3>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="workspaceName" className="block text-sm font-medium mb-1">
New Workspace Name
</label>
<input
id="workspaceName"
name="workspaceName"
type="text"
defaultValue={state.name}
disabled={isPending}
className="w-full rounded border px-3 py-2 text-sm disabled:opacity-50"
required
/>
</div>
{state.error && (
<p className="text-sm font-medium text-red-600" role="alert">
{state.error}
</p>
)}
<button
type="submit"
disabled={isPending}
className="rounded bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 disabled:bg-indigo-400"
>
{isPending ? 'Persisting Changes...' : 'Save Workspace'}
</button>
</form>
</div>
);
}Replacing forwardRef with Standard Ref Props
Since its introduction in React 16.3 in March 2018—spanning over six years of React development—passing a ref through a custom functional component required wrapping the implementation in React.forwardRef<T, P>(). This introduced awkward TypeScript generic syntax and broke prop inspection in developer tools.
In React 19, forwardRef is formally deprecated. Functional components now accept ref directly as a standard prop:
// React 18: Required forwardRef boilerplate
import React, { forwardRef } from 'react';
interface InputPropsLegacy extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
}
export const TextInputLegacy = forwardRef<HTMLInputElement, InputPropsLegacy>(
({ label, ...props }, ref) => (
<div className="flex flex-col">
<label className="text-sm">{label}</label>
<input ref={ref} {...props} />
</div>
)
);
TextInputLegacy.displayName = 'TextInputLegacy';
// React 19: Clean prop definition
interface TextInputPropsModern extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
ref?: React.Ref<HTMLInputElement>;
}
export function TextInputModern({ label, ref, ...props }: TextInputPropsModern) {
return (
<div className="flex flex-col">
<label className="text-sm font-medium">{label}</label>
<input
ref={ref}
{...props}
className="rounded border border-slate-300 px-3 py-1.5 focus:ring-2 focus:ring-blue-500"
/>
</div>
);
}Furthermore, React 19 introduces ref cleanup functions. If an imperative ref callback attaches an event listener or instantiates an external DOM observer, returning a cleanup function ensures automatic execution upon component unmount:
<div
ref={(element) => {
if (!element) return;
const resizeObserver = new ResizeObserver((entries) => {
console.log('DOM node resized:', entries[0].contentRect);
});
resizeObserver.observe(element);
// Cleanup function executes automatically on unmount
return () => {
resizeObserver.disconnect();
};
}}
/>Simplified Context API and Resource Hoisting
React 19 streamlines tree composition by allowing context objects to be rendered directly as providers, discarding <Context.Provider>:
import { createContext } from 'react';
export const SecurityContext = createContext<{ role: string }>({ role: 'viewer' });
export function SecurityScope({ role, children }: { role: string; children: React.ReactNode }) {
// Direct tag usage supported in React 19
return <SecurityContext value={{ role }}>{children}</SecurityContext>;
}In addition, React 19 includes native Document Metadata support. Components anywhere in the tree can emit <title>, <meta>, and <link> elements. React automatically hoists these tags into the HTML <head> during server rendering and hydration, removing the need for external packages like react-helmet-async.
---
React Compiler and Turbopack for Production Engineering
Manual performance optimization has long been a source of tech debt in React. Engineers spend countless sprint hours profiling re-renders, introducing useMemo, useCallback, and React.memo to preserve referential equality.
Automated Memoization and Dependency Graphs
Next.js 15 introduces experimental support for the React Compiler (originally developed under the codename React Forget). The React Compiler is an optimizing compiler that operates at build time. Using static analysis of JavaScript semantics and rules of React, the compiler automatically memoizes values, objects, functions, and JSX blocks without manual annotations.
To enable the compiler, install the required babel plugin:
pnpm add -D babel-plugin-react-compiler@latestThen configure next.config.ts:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
experimental: {
reactCompiler: true,
},
};
export default nextConfig;For large legacy codebases where certain components violate the Rules of React (such as mutating external variables during render), you can opt for targeted compilation using annotation mode:
const nextConfig: NextConfig = {
experimental: {
reactCompiler: {
compilationMode: 'annotation', // Only compiles files marked with "use memo"
},
},
};Turbopack Reaches General Availability for Development
Turbopack—the Rust-based successor to Webpack—is officially marked General Availability (GA) for local development in Next.js 15 (next dev --turbo), while Turbopack for production builds (next build) remains in active stabilization. In Vercel's official Next.js 15 release benchmarks evaluated on vercel.com, local development metrics demonstrated:
- Up to 76.7% faster local development server startup.
- Up to 96.3% faster Hot Module Replacement (HMR) and code updates with Fast Refresh.
- Up to 45.8% faster initial route compilation without caching for the local development server.
To enforce Turbopack across development environments, update your repository scripts:
{
"scripts": {
"dev": "next dev --turbo",
"build": "next build",
"start": "next start"
}
}Type-Safe next.config.ts Configuration
Next.js 15 adds first-class support for next.config.ts, providing compile-time type validation for all framework settings without needing JSDoc annotations:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
reactStrictMode: true,
poweredByHeader: false,
compress: true,
// Compiler configurations
experimental: {
reactCompiler: true,
ppr: 'incremental', // Partial Prerendering opt-in
staleTimes: {
dynamic: 0,
static: 180,
},
},
// Image optimization
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'assets.example.com',
pathname: '/**',
},
],
formats: ['image/avif', 'image/webp'],
},
// Output standalone for minimal Docker distribution
output: 'standalone',
};
export default nextConfig;---
Production Security: Hardening Server Actions
Server Actions are RPC (Remote Procedure Call) endpoints generated by Next.js when functions are marked with 'use server'. In Next.js 14, unreferenced Server Actions could still be compiled into client bundles, and action endpoints occasionally exposed predictable ID signatures.
Next.js 15 strengthens Server Action security through two primary architectural safeguards:
1. Dead-Code Elimination: If a Server Action is not directly referenced in a component tree, it is omitted from the client-side JavaScript manifest during next build.
2. Cryptographically Secure Non-Deterministic IDs: Action endpoint IDs are generated using random cryptographic hashes recalculated on every build. This prevents external penetration tools from discovering unindexed endpoints or guessing RPC identifiers across deployments.
To enforce enterprise-grade security, treat Server Actions like public REST or GraphQL endpoints. Never assume incoming arguments are sanitized:
// app/actions/billing.ts
'use server';
import { cookies } from 'next/headers';
import { z } from 'zod';
const InvoiceSchema = z.object({
invoiceId: z.string().uuid(),
amountCents: z.number().int().positive(),
});
export async function processInvoicePayment(formData: FormData) {
// 1. Authenticate identity via async request context
const cookieStore = await cookies();
const sessionToken = cookieStore.get('auth_token')?.value;
if (!sessionToken) {
throw new Error('Unauthorized invocation: Missing authentication token');
}
// 2. Validate input against strict schema
const parsed = InvoiceSchema.safeParse({
invoiceId: formData.get('invoiceId'),
amountCents: Number(formData.get('amountCents')),
});
if (!parsed.success) {
return { success: false, errors: parsed.error.flatten().fieldErrors };
}
// 3. Execute isolated database transaction
const { invoiceId, amountCents } = parsed.data;
console.log(`Processing invoice ${invoiceId} for ${amountCents} cents.`);
return { success: true, processedAt: new Date().toISOString() };
}---
Next.js 14 vs. Next.js 15 & React 19 Architectural Comparison
The following table summarizes the key architectural differences between Next.js 14 and Next.js 15 running React 19:
| Architectural Dimension | Next.js 14 (React 18) | Next.js 15 (React 19) | Production Impact |
|---|---|---|---|
Request Context APIs (cookies(), headers()) | Synchronous access | Asynchronous (Returns Promise<T>) | Prerequisite for Partial Prerendering (PPR); unblocks streaming concurrency. |
Route Parameters (params, searchParams) | Plain JavaScript Objects | Promises (Requires await or use()) | Eliminates blocking page-level execution during parameter resolution. |
| Default `fetch()` Caching | force-cache (Indefinite static storage) | no-store (Uncached, fresh evaluation) | Eliminates accidental stale data bugs; requires explicit opt-in for caching. |
| `GET` Route Handlers | Static by default | Dynamic by default | Prevents outdated JSON responses from custom API endpoints. |
| Client Router Page Cache | 30-second stale cache for dynamic routes | 0-second stale cache (staleTimes.dynamic: 0) | Instant consistency on client-side <Link> transitions. |
| Form & Mutation State | useFormState, useFormStatus | useActionState, useOptimistic | Standardized state machine for handling form lifecycles and latency compensation. |
| Component Ref Forwarding | React.forwardRef<T, P>() wrapper | Native ref prop on functional components | Streamlines component types; introduces ref cleanup functions. |
| Context Provider Syntax | <MyContext.Provider value={...}> | <MyContext value={...}> | Removes wrapper noise across deep dependency injection trees. |
| Memoization Strategy | Manual (useMemo, useCallback, React.memo) | Automated via React Compiler | Drastically reduces boilerplate and eliminates referential memory bugs. |
| Build & Config Tooling | Webpack default; next.config.js | Turbopack GA for dev; native next.config.ts | Faster local iteration; compile-time configuration validation. |
---
Enterprise Migration Blueprint & Verification Checklist
Migrating large enterprise production systems requires a disciplined, multi-step modernization plan. When upgrading high-traffic platforms, architectural teams often engage specialized partners like the Wise Hustlers web development consulting group to audit data access patterns, configure resilient edge caching tiers, and manage zero-downtime framework migrations.
Phased Upgrade Strategy and Codemods
Follow this sequence to execute the migration with minimal disruption:
Phase 1: Dependency Alignment
Update Next.js, React, and their associated type packages to the latest matching versions:
pnpm add next@latest react@latest react-dom@latest
pnpm add -D @types/react@latest @types/react-dom@latest eslint-config-next@latestPhase 2: Automated Codemod Execution
Run the official Next.js codemod to convert synchronous request API calls (cookies, headers, params) into asynchronous counterparts across all layouts, pages, and route handlers:
npx @next/codemod@canary next-async-request-api .Next, run the React 19 codemod to migrate deprecated forwardRef definitions to standard prop signatures:
npx codemod@latest react/19/replace-forward-refPhase 3: Audit Backend Data Caching
Review all fetch() calls across your data-access layer. For any external API that does not require real-time execution, explicitly specify { cache: 'force-cache' } or add next: { revalidate: <seconds> } to avoid unexpected latency spikes on internal microservices.
Diagnostic Evidence and Falsifiability Matrix
To ensure that the migration has succeeded without silent regressions, verify your build artifacts against the following falsifiability matrix:
| Failure Mode / Diagnostic Target | Primary Technical Evidence | Falsifiability Test ("How do we know it failed?") | Remediation Protocol |
|---|---|---|---|
| Un-awaited Request APIs | Runtime console error: A dynamic server usage was detected: synchronous access to cookies(). | Run pnpm tsc --noEmit. TypeScript compiler fails with error Property 'get' does not exist on type 'Promise<ReadonlyRequestCookies>'. | Add await keyword before cookies() or headers() call. |
| Unintended Data Cache Misses | Inspect response header: x-nextjs-cache returns MISS or BYPASS on consecutive queries. | Issue two identical curl requests: curl -I https://app.corp/api/catalog. If response times remain identical and cache headers report MISS, caching failed. | Add { cache: 'force-cache' } or next: { revalidate: 3600 } to data fetchers. |
| Stale State on Client Navigation | Client router displays outdated database values after redirecting from a mutation. | Perform a mutation, navigate via <Link> to the index view, and inspect DOM. If old data persists without page reload, the client cache is stale. | Set staleTimes: { dynamic: 0 } inside next.config.ts. |
| React Compiler Rule Violations | Build failure or terminal warning: React Compiler has skipped optimizing ComponentX due to rule violations. | Run next build with experimental.reactCompiler: true. Check build output for compiler optimization logs. | Use eslint-plugin-react-compiler to identify and refactor local state mutations during render. |
| Dangling Server Actions | Bundled client chunks contain dead RPC identifiers. | Search production .next/static/chunks/ for unreferenced action string hashes. If present, dead-code elimination failed. | Verify that action exports are encapsulated in isolated files marked 'use server' at top level. |
---
Frequently Asked Questions
How does the async params breaking change impact dynamic routing in Next.js 15 Client Components?
In Next.js 15, dynamic route parameters (params and searchParams) are passed to page.tsx as Promises. In Server Components, you unwrap them using standard await params. In Client Components ('use client'), you cannot declare the component function itself as async. Instead, you pass the params Promise down from the server page and resolve it using React 19's use() hook: const { id } = use(paramsPromise). Alternatively, you can use the useParams() and useSearchParams() hooks from next/navigation, which continue to handle client-side parameter synchronization internally.
Will upgrading to Next.js 15 increase database load because fetch is no longer cached by default?
Yes, if your application relied on Next.js 14's default force-cache behavior without explicit cache configurations. In Next.js 15, all unconfigured fetch() calls default to no-store. If your Server Components query your primary database or microservices during every request, backend traffic will increase significantly. To preserve your caching topology, audit your data access layer prior to deployment and explicitly add { cache: 'force-cache' } or next: { revalidate: 3600, tags: [...] } to all read-heavy queries.
Can we immediately remove all useMemo and useCallback hooks once the React Compiler is enabled?
You do not need to perform an immediate, sweeping removal of existing useMemo and useCallback hooks. The React Compiler safely processes codebases containing manual memoization primitives. However, as your team writes new features, you should avoid adding manual memoization, allowing the compiler to manage dependency graphs automatically. If you wish to clean up legacy code, do so incrementally with full component test coverage, ensuring that your components strictly follow the Rules of React and avoid mutating shared objects during render.
How does React 19 handle backward compatibility with component libraries still using forwardRef?
React 19 maintains backward compatibility with React.forwardRef(). Third-party UI component libraries (such as Radix UI, Headless UI, or internal design systems) authored with forwardRef will continue to render and receive refs without breaking. However, forwardRef is now marked for future deprecation. Modern internal design system components should be refactored to accept ref directly as a standard prop, simplifying type signatures and preparing your codebase for subsequent major releases.
---