Next.js View Transitions: React 19.2 Native Animation Guide
Implement smooth page transitions in Next.js 16 with React 19.2 View Transitions API.
20+ years shipping production JavaScript and front-end systems at scale. Notes here come from systems that actually shipped.
- ✓Next.js 15.2+, React 19.2, Node.js 20+, familiarity with App Router, CSS animations, and basic React hooks (useState, useEffect).
- Enable View Transitions in Next.js 16 with
viewTransition: truein next.config.ts - Powered by React 19.2 — uses the browser-native View Transitions API
- Works with
component and router.navigate() automatically - Customize transitions with CSS
::view-transition-*pseudo-elements - Cross-document transitions work across same-origin navigations
- No external animation library required — pure CSS and browser API
View Transitions are like a seamless magic trick between pages. Instead of the old page vanishing and the new page appearing (a hard cut), the browser smoothly morphs one into the other. Your content slides, fades, or transforms naturally — like turning pages in a book instead of slamming it shut and opening a new one.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
View Transitions arrived in React 19.2 and are fully supported in Next.js 16. This browser-native API enables smooth animated transitions between routes without JavaScript animation libraries. Pages morph seamlessly — shared elements (like a hero image or site header) animate smoothly between states, while content areas cross-fade naturally.
This guide covers everything from basic setup to custom animations, cross-document transitions, and production debugging.
Why View Transitions Matter in Next.js
Traditional client-side routing in Next.js often results in abrupt content swaps, causing jarring UX and accessibility issues. The View Transitions API, now native to React 19.2, provides a browser-level mechanism to animate between DOM states. In Next.js, this is especially powerful because it works seamlessly with Server Components and streaming. By wrapping route changes or state updates in startViewTransition, you get smooth crossfades, morphs, and slides without layout thrashing. This is critical for production apps where perceived performance directly impacts conversion rates. The API is declarative: you define the transition style via CSS pseudo-elements (::view-transition-old, ::view-transition-new), and the browser handles the rest. No more manual requestAnimationFrame loops or third-party animation libraries.
Setting Up View Transitions in Next.js 15+
To use View Transitions with Next.js, you need React 19.2 and Next.js 15.2+. The next-view-transitions package provides a <ViewTransitions> component that wraps your layout and automatically intercepts route changes. Install it via npm install next-view-transitions. Then, wrap your root layout with <ViewTransitions>. This component uses the new startViewTransition API from React 19.2 under the hood. For page-level transitions, you can also use the useViewTransition hook to trigger transitions on state changes. The setup is minimal: no configuration files, no webpack plugins. The library handles both client-side navigations (via next/link) and programmatic router.push calls. For Server Components, the transition is triggered automatically on the client after the server response.
<ViewTransitions> to enable automatic route transition animations.Customizing Transition Animations with CSS
The default View Transition is a crossfade. To customize, use CSS pseudo-elements ::view-transition-old and ::view-transition-new on the transition root. You can animate properties like opacity, transform, and clip-path. For example, to create a slide-up effect, set the old page to fade out and slide down, while the new page slides up. Use animation or transition properties. You can also target specific elements by assigning them a view-transition-name CSS property. This is useful for animating shared elements (e.g., a product image that morphs from list to detail). The browser captures snapshots of the old and new states and animates between them. Performance is hardware-accelerated because the compositor handles the animation.
Animating Shared Elements Between Pages
Shared element transitions (morphing) are the killer feature of View Transitions. By assigning a view-transition-name to an element that appears on both pages, the browser will smoothly morph it from old to new state. For example, a product image on a listing page can expand into the hero image on the detail page. To implement, add style={{ viewTransitionName: 'product-image' }} to the element in both pages. Ensure the name is unique per transition. The browser captures the element's position, size, and shape, then animates the transformation. This creates a fluid, app-like experience. However, be careful with elements that change aspect ratio — the morph can look distorted. Use object-fit to control image scaling.
product-${id}) to avoid conflicts when multiple elements share the same name.Triggering Transitions on State Changes
View Transitions aren't limited to route changes. You can trigger them on any state update using the useViewTransition hook from next-view-transitions. This is useful for list reordering, toggling modals, or filtering content. The hook returns a startViewTransition function that wraps a state update. Inside the callback, you update state as usual. The browser captures the before and after snapshots and animates between them. For example, to animate a list when items are reordered, call startViewTransition(() => setItems(newOrder)). The browser will smoothly move each item to its new position. This is much simpler than using FLIP animations manually.
startViewTransition to animate any state change, not just route navigations.Handling Async Data and Loading States
View Transitions work best when both old and new states are ready. For async data (e.g., fetching a new page), you need to coordinate the transition with data loading. The typical pattern is to start the transition, fetch data, then update state. However, the browser captures the snapshot immediately, so if the new content isn't ready, you'll see a flash. To avoid this, use React's use hook or Suspense to stream the new content. Alternatively, you can delay the transition until data is ready by calling startViewTransition inside a useEffect after fetch completes. For Next.js App Router, the built-in streaming with Suspense works naturally: the transition starts when the new page's Suspense boundary resolves.
Accessibility Considerations
View Transitions can cause motion sickness or disorientation for users with vestibular disorders. Always respect the prefers-reduced-motion media query. You can disable transitions entirely or replace them with a simple fade. Use CSS to conditionally apply animations: @media (prefers-reduced-motion: reduce) { ::view-transition-old, ::view-transition-new { animation: none; } }. Additionally, ensure that transitions don't interfere with focus management. After a transition, the browser should focus the new page's main heading or the first interactive element. Next.js handles focus by default, but if you use custom transitions, you may need to manually manage focus with useEffect.
element.focus() on the new page's main content to help screen reader users.prefers-reduced-motion and manage focus to keep transitions accessible.Debugging View Transitions
Debugging View Transitions can be tricky because they involve browser compositing. Use Chrome DevTools' Rendering tab to enable 'View Transitions' debugging. This shows the snapshot captures and animation timelines. Common issues: transitions not running because the provider is missing, or elements not having unique viewTransitionName. Check the console for warnings like 'View transition failed: element not found'. Also, ensure your CSS pseudo-elements are correctly scoped. If transitions are janky, reduce the number of animated elements or simplify animations. Use will-change: transform on animated elements to hint the browser. For complex transitions, profile with the Performance tab to identify compositor bottlenecks.
Performance Optimization
View Transitions are compositor-threaded, meaning they don't block the main thread. However, capturing snapshots of large DOM trees can be expensive. To optimize, avoid animating the entire page if only a small part changes. Use view-transition-name on specific containers instead of the root. Also, minimize the number of animated properties — prefer opacity and transform over width or height. For list animations, use contain: layout style paint on list items to reduce paint area. In Next.js, leverage Server Components to reduce client-side JavaScript, which speeds up snapshot capture. Profile with Lighthouse to ensure transitions don't negatively impact Largest Contentful Paint (LCP).
contain to reduce paint overhead.Fallback Strategies for Unsupported Browsers
Not all browsers support View Transitions. For unsupported browsers, you need a fallback that either does nothing or uses a simple CSS transition. Use feature detection with document.startViewTransition check. In the next-view-transitions library, the provider automatically falls back to no animation if unsupported. For custom transitions, wrap your startViewTransition call in a condition: if (document.startViewTransition) { ... } else { updateState(); }. You can also use a polyfill like view-transitions-polyfill (npm package) that implements the API using CSS animations. However, polyfills may not be as performant. For production, consider progressive enhancement: smooth transitions on modern browsers, instant navigation on older ones.
Testing View Transitions in CI/CD
Testing animations in automated CI is challenging because they are visual. Use Playwright or Cypress to take screenshots before and after transitions and compare them. For unit tests, mock document.startViewTransition to verify it's called with the correct callback. For integration tests, use page.waitForFunction to check that the transition pseudo-elements are present. Also, test with prefers-reduced-motion: reduce to ensure fallbacks work. In Next.js, you can use the next-view-transitions test utilities if available. Always run visual regression tests on a per-page basis to catch unintended animation changes.
waitForFunction that checks for the absence of pseudo-elements.Real-World Production Patterns
In production, we use View Transitions for three main patterns: page transitions, shared element morphing (e.g., product image), and state-based animations (e.g., cart updates). For page transitions, we keep it simple: a 200ms crossfade with a slight scale. For shared elements, we use viewTransitionName with dynamic IDs. For state updates, we wrap critical interactions like 'add to cart' with startViewTransition to animate the cart icon badge. One anti-pattern: don't animate the entire layout when only a small component changes. Instead, scope the transition to that component. Also, avoid animating elements that change size unpredictably (e.g., text blocks with dynamic content). Use contain: layout to prevent reflows.
| File | Command / Code | Purpose |
|---|---|---|
| app | export default function RootLayout({ children }: { children: React.ReactNode }) ... | Why View Transitions Matter in Next.js |
| app | export default function Home() { | Setting Up View Transitions in Next.js 15+ |
| app | ::view-transition-old(root) { | Customizing Transition Animations with CSS |
| app | export default function ProductDetail({ params }: { params: { id: string } }) { | Animating Shared Elements Between Pages |
| app | 'use client'; | Triggering Transitions on State Changes |
| app | export default function Dashboard() { | Handling Async Data and Loading States |
| app | @media (prefers-reduced-motion: reduce) { | Accessibility Considerations |
| app | 'use client'; | Debugging View Transitions |
| app | .list-item { | Performance Optimization |
| app | 'use client'; | Fallback Strategies for Unsupported Browsers |
| e2e | test('should animate route change', async ({ page }) => { | Testing View Transitions in CI/CD |
| app | 'use client'; | Real-World Production Patterns |
Key takeaways
viewTransitionName to elements that appear on both pages for seamless morphing animations.contain to avoid jank.prefers-reduced-motion and manage focus to ensure inclusive UX.Frequently Asked Questions
20+ years shipping production JavaScript and front-end systems at scale. Notes here come from systems that actually shipped.
That's Next.js. Mark it forged?
5 min read · try the examples if you haven't