React 19.2 has been released, and with it the React Compiler graduates to stable. This isn't a minor version bump — it marks the point where React's optimization model fundamentally changes. Manual memoization as a default practice starts to retire. Meanwhile, TypeScript has crossed a threshold in 2026: the question is no longer "should we use TypeScript?" but "are we using it well?"
What the React Compiler Actually Does
The React Compiler performs static analysis on your component code at build time and inserts memoization where it determines values can be safely cached. The goal is to eliminate the need for developers to manually write useMemo, useCallback, and React.memo in most cases.
The Problem It Solves
Before the compiler, React developers had to manually manage memoization to prevent unnecessary re-renders:
// Before compiler: manual memoization
const UserCard = React.memo(({ user, onDelete }: Props) => {
const formattedDate = useMemo(
() => formatDate(user.createdAt),
[user.createdAt]
);
const handleDelete = useCallback(
() => onDelete(user.id),
[onDelete, user.id]
);
return (
<div>
<span>{user.name}</span>
<span>{formattedDate}</span>
<button onClick={handleDelete}>Delete</button>
</div>
);
});The problem isn't that this code is hard to write — it's that the dependency arrays are easy to get wrong. A stale closure or missing dependency causes either performance regressions (missing optimization) or bugs (stale values). This pattern is particularly error-prone for developers new to React.
After the Compiler
With React Compiler enabled, you write the straightforward version and the compiler handles the rest:
// After compiler: no manual memoization needed
const UserCard = ({ user, onDelete }: Props) => {
const formattedDate = formatDate(user.createdAt);
const handleDelete = () => onDelete(user.id);
return (
<div>
<span>{user.name}</span>
<span>{formattedDate}</span>
<button onClick={handleDelete}>Delete</button>
</div>
);
};The compiler analyzes which values change together and emits optimized output. The mental model for developers simplifies considerably.
Enabling It in Next.js
React Compiler is supported in Next.js 15.x. Enabling it is a one-line change:
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
reactCompiler: true,
},
};
export default nextConfig;You don't need to remove existing useMemo or useCallback calls immediately — the compiler handles code that has them too. The cleanup can happen incrementally. The priority is getting the compiler running first.
Patterns the Compiler Can't Optimize
The compiler works best when your code follows React's rules. It will skip optimization (and warn) when it encounters:
- Mutations of props or state directly
useEffectcallbacks with mixed, unrelated side effects- Custom hooks that return different references for the same input
The eslint-plugin-react-compiler package catches these patterns before they become runtime surprises:
npm install --save-dev eslint-plugin-react-compiler// .eslintrc.json
{
"plugins": ["react-compiler"],
"rules": {
"react-compiler/react-compiler": "error"
}
}Getting this lint rule clean is a prerequisite for trusting the compiler's output.
TypeScript in 2026: Using It Well
TypeScript adoption has reached the point where it's a baseline expectation on professional projects. The shift now is from "we have TypeScript" to "we're getting value from TypeScript." Those aren't the same thing.
The failure mode is treating TypeScript as a box-checking exercise: enabling it, then using any liberally to silence errors. TypeScript's value comes from letting it constrain your design.
Design Through Types
// This isn't TypeScript being used well
function processPayment(data: any) {
return fetch('/api/payment', {
method: 'POST',
body: JSON.stringify(data),
});
}
// This is
type PaymentRequest = {
amount: number;
currency: 'JPY' | 'USD' | 'EUR';
customerId: string;
description?: string;
};
async function processPayment(
data: PaymentRequest
): Promise<{ transactionId: string }> {
const res = await fetch('/api/payment', {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});
return res.json();
}The PaymentRequest type forces callers to pass valid currency codes and prevents numeric/string confusion. It also documents the API — any editor will show the allowed structure on hover without requiring documentation lookup.
React 19 Type Improvements
React 19 ships significantly improved types for Server Components and the Actions API:
// Server Action with proper types
'use server';
export async function createPost(
prevState: { error: string | null },
formData: FormData
): Promise<{ error: string | null }> {
const title = formData.get('title');
if (typeof title !== 'string' || title.length === 0) {
return { error: 'Title is required' };
}
// Save to database...
return { error: null };
}Paired with useActionState, this gives you a type-safe pattern for surfacing server-side validation errors in forms without client-side duplication.
Practical Migration Strategy
New Projects
Start with React 19.2, TypeScript strict mode, and the React Compiler all enabled from day one:
npx create-next-app@latest my-app \
--typescript \
--tailwind \
--eslint \
--appIn tsconfig.json, set "strict": true. Retrofitting strict mode onto an existing codebase is expensive — the cost is low when you start with it.
Existing Projects
A phased approach reduces risk:
- Enable React Compiler (low risk): One line in
next.config.mjs. Existing code doesn't need changes - Incrementally tighten TypeScript: Start with
noImplicitAnyrather than full strict mode; file by file is sustainable - Remove redundant memoization: Once the compiler is running stably, clean up
useMemo/useCallbackin components where the compiler handles it
Avoid trying to do all three simultaneously — it makes it hard to attribute any regressions.
Takeaway
React Compiler reaching stable is the framework signaling that performance optimization is increasingly its responsibility, not the developer's. Combined with TypeScript's maturation as a default, the baseline for professional React development has risen. The practical implication isn't that existing codebases need immediate rewrites — it's that the default starting point for new work has shifted, and the old patterns (manual memoization everywhere, TypeScript as optional) are becoming legacy choices.