React 19.2 + Next.js 16 Cache Components: View Transitions, useEffectEvent, and Partial Pre-Rendering
React 19.2 brings ViewTransition, useEffectEvent, and the Activity API. Paired with Next.js 16 Cache Components (PPR + use cache), discover how to build ultra-fast apps with instant navigation.
React 19.2 + Next.js 16 Cache Components: View Transitions, useEffectEvent, and Partial Pre-Rendering
React 19.2 is the third major release of 2025, and the most substantial. Paired with Next.js 16 and its Cache Components, it redefines what a web application can do in terms of performance and user experience.
React 19.2, released October 1, 2025, introduces three major features: the ViewTransition component, the useEffectEvent hook, and the Activity API. Next.js 16, released in July 2026, brings Cache Components — a new programming model combining Partial Pre-Rendering (PPR) and the use cache directive for instant navigation.
Together, these two releases transform how we build web applications: native animations without third-party libraries, stable effects without unnecessary re-renders, and partial pre-rendering for pages that are both static and dynamic. This guide explores each feature with concrete examples and real use cases.
Overview: The React 19.2 + Next.js 16 Ecosystem
Map of React 19.2 and Next.js 16 features
If you're on Next.js 16, you're already using React 19.2 because the App Router ships with React Canary. No separate installation is needed. For standalone React projects:
npm install react@19.2 react-dom@19.2
npm install eslint-plugin-react-hooks@latest # v6.1.1+ for useEffectEvent linting
ViewTransition: Native Declarative Transitions
The Problem
Historically, animating route transitions or state changes in React required a third-party library (Framer Motion, react-transition-group) and significant plumbing. The browser's native document.startViewTransition() API exists, but its manual coordination with React's render cycle is complex.
The React 19.2 Solution
The <ViewTransition> component handles this coordination automatically. It activates when:
- Content changes via Suspense boundaries (fallback → content)
- A state change within a
startTransition - A deferred value via
useDeferredValue
import { ViewTransition } from 'react';
function ProductGallery({ products, selectedId }) {
return (
<ViewTransition>
<ProductList products={products} />
<ProductDetail id={selectedId} />
</ViewTransition>
);
}
Shared-Element Transitions
The flagship feature: shared-element transitions, where an element appears to move fluidly from one layout to another. For example, a product image that transforms into a full-size image on click.
<ViewTransition>
{view === 'grid' && <ProductCard product={product} />}
{view === 'detail' && <ProductFullView product={product} />}
</ViewTransition>
// In ProductCard:
<img
style={{ viewTransitionName: `product-${product.id}` }}
src={product.image}
/>
// In ProductFullView:
<img
style={{ viewTransitionName: `product-${product.id}` }}
src={product.image}
/>
The browser automatically animates the transition between the two elements sharing the same view-transition-name.
Events and Web Animations API
ViewTransition supports optional callbacks to control animation via JavaScript:
<ViewTransition
onEnter={(event) => {
// Customize enter animation
const animation = event.snapshot.animate([
{ opacity: 0, transform: 'scale(0.8)' },
{ opacity: 1, transform: 'scale(1)' },
], { duration: 300 });
return () => animation.cancel();
}}
onExit={(event) => { /* ... */ }}
onShare={(event) => { /* ... */ }}
onUpdate={(event) => { /* ... */ }}
>
<Content />
</ViewTransition>
Important rules:
- Only one event fires per
<ViewTransition>per Transition onSharetakes precedence overonEnterandonExit- Each event must return a cleanup function
Browser Support
As of May 2026, global support for same-document View Transitions is around 78%:
- Chrome 111+, Edge 111+
- Safari 18+ (including iOS 18)
- Firefox: partial support
The React component handles degradation automatically on unsupported browsers.
useEffectEvent: Stable Effects Without Re-renders
The Problem Solved
The classic case: a chat connection that reconnects unnecessarily every time the theme changes, just because the onConnect callback reads the theme value.
// BEFORE: unnecessary reconnection on every theme change
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', () => {
showNotification('Connected!', theme); // theme in deps
});
connection.connect();
return () => connection.disconnect();
}, [roomId, theme]); // ← theme causes re-execution
}
The Solution
useEffectEvent extracts non-reactive logic out of the dependency array:
// AFTER: stable connection, theme always up-to-date
import { useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', onConnected);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ← theme no longer in deps
}
Usage Rules
Decision tree for when to use useEffectEvent
- Declare in the same component as the Effect that uses it
- Don't pass to children — Effect Events are limited to the owning component
- Use
eslint-plugin-react-hooks@latest(v6.1.1+) for linting - Don't wrap everything — useEffectEvent is for event-like logic, not for silencing lint
Activity API: Preserve State Without Rendering
The Activity API keeps parts of your UI mounted but hidden, preserving their state while unmounting effects and deferring updates.
import { Activity } from 'react';
function TabContainer({ activeTab }) {
return (
<>
<Activity mode={activeTab === 'profile' ? 'visible' : 'hidden'}>
<ProfileTab />
</Activity>
<Activity mode={activeTab === 'settings' ? 'visible' : 'hidden'}>
<SettingsTab />
</Activity>
</>
);
}
Typical use cases:
- Tabs where the user switches frequently
- Modals with form state to preserve
- Virtualized lists with scroll position to maintain
Next.js 16 Cache Components: PPR + use cache
The Programming Model
Cache Components in Next.js 16 represent a fundamental shift: instead of choosing between static and dynamic at the page level, you can now statically pre-render portions of pages while keeping other parts dynamic.
Partial Pre-Rendering and Cache Components architecture in Next.js 16
Partial Pre-Rendering (PPR)
PPR allows Next.js to serve a static shell immediately, then stream dynamic parts:
// app/products/[id]/page.tsx
import { Suspense } from 'react';
export const experimental_ppr = true;
export default function ProductPage({ params }) {
return (
<div>
{/* Static: pre-rendered at build, served immediately */}
<ProductHeader id={params.id} />
{/* Dynamic: streamed after */}
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={params.id} />
</Suspense>
</div>
);
}
use cache Directive
The use cache directive lets you explicitly mark segments as cacheable:
// Static cache at build
async function ProductHeader({ id }) {
'use cache';
const product = await fetch(`/api/products/${id}`).then(r => r.json());
return <h1>{product.name}</h1>;
}
// Cache with revalidation
async function getRecommendedProducts(userId) {
'use cache';
const products = await fetch(`/api/recommendations/${userId}`).then(r => r.json());
return products;
}
// Cache with tag for invalidation
async function getUserProfile(userId) {
'use cache';
const user = await db.user.findUnique({ where: { id: userId } });
return user;
}
// Invalidation: revalidateTag('user-profile');
New Cache APIs
Next.js 16 introduces refined cache APIs:
| API | Usage |
|---|---|
updateTag() | Invalidates and recomputes a specific cache tag |
refresh() | Forces cache refresh |
revalidateTag() | Invalidates cache for a tag (refined) |
// Action that invalidates cache after mutation
'use server';
import { revalidateTag } from 'next/cache';
export async function updateProfile(userId, data) {
await db.user.update({ where: { id: userId }, data });
revalidateTag('user-profile');
}
Turbopack Stable: The Default Bundler
Next.js 16 makes Turbopack the default bundler for all applications. Benefits:
- Faster startup and compilation (Rust-based)
- Turbopack File System Caching (beta): even faster startup
- React Compiler (stable): built-in integration for automatic memoization
// next.config.ts
export default {
// Turbopack is now the default, no flag needed
// To disable (not recommended):
// turbopack: false,
};
Comparison: React 19.1 vs 19.2 + Next.js 15 vs 16
Radar comparison of capabilities between React 19.1/19.2 and Next.js 15/16
The radar above compares five dimensions: Performance (Turbopack, PPR), DX (React Compiler, useEffectEvent), Animations (ViewTransition), Caching (Cache Components), and Stability (API maturity).
Migration Plan: From Next.js 15 + React 19.1 to 16 + 19.2
Step 1: Update Dependencies
npm install next@latest react@19.2 react-dom@19.2
npm install eslint-plugin-react-hooks@latest
Step 2: Breaking Changes to Handle
// Async params (Next.js 16 breaking change)
// BEFORE (Next.js 15):
export default function Page({ params }) {
return <Product id={params.id} />;
}
// AFTER (Next.js 16):
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <Product id={id} />;
}
Step 3: Progressive Adoption of Cache Components
// 1. Enable PPR on a page
export const experimental_ppr = true;
// 2. Identify static segments
async function StaticHeader() {
'use cache';
const data = await fetch('https://api.example.com/config').then(r => r.json());
return <Header config={data} />;
}
// 3. Keep dynamic segments in Suspense
<Suspense fallback={<Skeleton />}>
<DynamicContent />
</Suspense>
Step 4: Migrate Effects to useEffectEvent
Identify effects that re-run too often due to non-reactive dependencies. Extract event-like logic with useEffectEvent.
Real Performance: What Cache Components Change
Based on Next.js benchmarks and field reports, Cache Components bring:
| Metric | Without PPR | With PPR | Gain |
|---|---|---|---|
| FCP (First Contentful Paint) | 1.1s | 0.3s | -73% |
| LCP (Largest Contentful Paint) | 2.4s | 0.8s | -67% |
| TTFB | 180ms | 45ms | -75% |
| TBT (Total Blocking Time) | 290ms | 50ms | -83% |
These figures are estimates based on Next.js documentation and Vercel team tests. Real gains depend on the proportion of static vs dynamic content in your pages.
Conclusion
React 19.2 and Next.js 16 represent the most significant duo for front-end web development in 2026. View Transitions eliminate the need for third-party animation libraries. Cache Components with PPR make obsolete the historical trade-off between static and dynamic. useEffectEvent solves a 10-year-old papercut in effect management.
For teams hesitant to migrate, the path is progressive: you can adopt these features one at a time, page by page. If your company wants to accelerate this migration, our web development expertise covers Next.js 16 migrations with performance optimization, and our SaaS development service integrates Cache Components from the initial design.
The React 19.2 + Next.js 16 combination isn't an incremental update — it's a paradigm shift toward web applications that rival native in fluidity and performance.
Tags
FAQ
What is ViewTransition in React 19.2?
ViewTransition is a declarative component in React 19.2 that automatically handles browser view transitions when content changes via Suspense, state changes, or navigation. It supports shared-element transitions and onEnter/onExit events via the Web Animations API.
When should I use useEffectEvent?
useEffectEvent should be used when an Effect re-runs too often because of a non-reactive value in its dependencies. Typical case: a chat connection that reconnects when the theme changes, just because the onConnect callback reads the theme. useEffectEvent extracts this logic out of the dependencies.
What are Cache Components in Next.js 16?
Next.js 16 Cache Components combine Partial Pre-Rendering (PPR) and the `use cache` directive to statically pre-render portions of pages while keeping other parts dynamic. This enables instant navigation without sacrificing personalization.
Is ViewTransition supported by all browsers?
As of May 2026, global support is around 78%. Chrome 111+, Edge 111+, and Safari 18+ (including iOS 18) support same-document View Transitions. Firefox has partial support. The React ViewTransition component handles degradation automatically.
How do I migrate to React 19.2 with Next.js 16?
If you're on Next.js 16, you're already using React 19.2 because the App Router ships with React Canary. For standalone React projects, install `react@19.2` and `eslint-plugin-react-hooks@latest` (v6.1.1+) for useEffectEvent linting.
Ready to implement this?
Book a free 30-min strategy call with our experts
We'll analyze your situation and propose a concrete action plan.

William Aklamavo
Web development and automation expert, passionate about technological innovation and digital entrepreneurship.
