View Transitions in Next.js: Animated Page and Element Changes Without a Library
Table of Contents
About the Author

Ahmed Mahmoud
Author & Developer
Software engineer passionate about web development and user experience design.
Founder of Devya · eng-ahmed.com ↗Key takeaways
The View Transitions API animates between two DOM states when you call document.startViewTransition(updateDOM); the browser snapshots the page before and after the callback and animates the difference.
The default transition is a full-page cross-fade named root; assigning view-transition-name to an element animates it independently as its own group.
For Next.js App Router client navigations, our team wraps router.push in document.startViewTransition, or enables React's built-in ViewTransition component behind the experimental.viewTransition flag.
React's ViewTransition component is still experimental in React 19.x — it is opt-in behind a flag, not part of the stable API.
A duplicate view-transition-name in a single snapshot makes the browser skip the transition entirely, so every name must be unique per snapshot.
What is the View Transitions API, and what problem does it solve?
The View Transitions API is a browser API that animates the visual change between two states of the DOM. Before it existed, animating a route change meant keeping both the old and new UI in the DOM at once, coordinating CSS classes, and cleaning up afterwards — effectively a library's whole job.
At Devya we replaced that machinery with the native API. You call document.startViewTransition(callback), the browser screenshots the current page, runs the callback to update the DOM, screenshots the new state, and cross-fades between the two snapshots. Same-document transitions shipped in Chrome 111 in March 2023, and cross-document navigations shipped in Chrome 126 in June 2024, with Firefox and Safari adding the same-document API since.
How do we run a first view transition?
Call document.startViewTransition() and pass a callback that mutates the DOM; the default animation is a quarter-second cross-fade of the whole page. Feature-detect the method first, because it is undefined in browsers without support, and calling the update directly is the correct fallback.
The method returns a ViewTransition object with three promises: updateCallbackDone when the callback has run, ready when the pseudo-elements are built and the animation is about to start, and finished when the animation ends. Awaiting finished lets our code run after the transition completes.
How do we animate one element independently?
Give the element a unique view-transition-name in CSS and the browser animates it as its own group instead of folding it into the page cross-fade. This is what produces the shared-element effect, where a thumbnail grows into a hero image across a navigation.
During a transition the browser builds pseudo-elements our stylesheets can target: ::view-transition-old(name) is the outgoing snapshot, ::view-transition-new(name) is the incoming one, and ::view-transition-group(name) wraps both and animates position and size. The name root is assigned to the whole document by default, so ::view-transition-old(root) targets the full-page cross-fade.
How do we add view transitions to Next.js App Router navigation?
Wrap the navigation in document.startViewTransition so the DOM update React performs during the route change becomes the transition's callback. Because router.push is asynchronous, the reliable pattern is a small client hook that calls document.startViewTransition with a callback that pushes the new route, and falls back to a plain push when the API is missing.
For cross-document transitions in a statically exported or multi-page site, no JavaScript is needed at all: opt in with the CSS at-rule @view-transition { navigation: auto; } in both the source and destination pages. The community next-view-transitions package wraps the client pattern for teams that prefer not to write the hook themselves.
What is React's ViewTransition component, and is it ready?
ViewTransition is an experimental React component that animates its children when they enter, exit, or reorder as a result of a transition or a Suspense boundary resolving. Instead of calling document.startViewTransition directly, you wrap the changing UI and React drives the API for you.
It is still experimental in React 19.x: the import is prefixed unstable_ViewTransition, and in Next.js it is enabled with experimental.viewTransition set to true in next.config. At Devya we ship the plain document.startViewTransition approach in production because it is a stable browser API, and reserve the React component for prototypes until it reaches a stable release. The trade-off is direct: the React component gives enter and exit animations tied to the component tree, while the raw API gives stability today.
What breaks — duplicate names, reduced motion, and performance
The most common failure is two elements sharing one view-transition-name in the same snapshot; the browser cannot decide which element to morph, so it skips the transition and logs a warning. For a list, assign a name derived from a unique id and only to the item that is actually transitioning.
Honor prefers-reduced-motion, because the API animates and users who ask for less motion should get the instant update; disabling the animation on the view-transition pseudo-elements inside that media query is enough. On performance, the old and new states render as snapshots and the group animation moves them with transforms, which the compositor handles cheaply, but large full-screen snapshots on low-end devices can still cost frames — so we keep independently-named transitions to the elements that genuinely need to move and let everything else ride the default cross-fade.
FAQ
Which browsers support the View Transitions API?
Same-document transitions shipped in Chrome 111 in March 2023 and are now in Firefox and Safari; cross-document transitions shipped in Chrome 126 in June 2024. Always feature-detect document.startViewTransition.
Do we need a library to use view transitions in Next.js?
No. Wrapping router.push in document.startViewTransition covers client navigations with no dependency, and libraries like next-view-transitions simply package that pattern.
Why did a view transition not animate?
The most common cause is two elements with the same view-transition-name in one snapshot, which makes the browser skip the transition. Names must be unique per captured state.
Is React's ViewTransition component stable?
No. As of React 19.x it is experimental, imported as unstable_ViewTransition and enabled in Next.js behind experimental.viewTransition. Use the browser API directly for production.
Can view transitions animate layout properties like width and height?
Yes, through ::view-transition-group, which animates size and position between snapshots. Keep heavier animations to transform and opacity where possible, since the old and new states are rendered as images.
Further Reading
Frontend Engineering
URL State in the Next.js App Router: Type-Safe Search Params That Survive a Refresh
We move filters, tabs, and pagination out of useState and into the URL query string, so a refresh or a shared link restores the exact view. Here is how our team reads and writes search params in the Next.js App Router, and when nuqs earns its place.
Frontend Engineering
Error Handling in the Next.js App Router: What Each Error File Actually Catches
In the Next.js App Router, error.tsx defines an error boundary that catches render errors in a route segment and its children — but not its own layout, and not event-handler errors. Our engineering notes on error.tsx, global-error.tsx, not-found.tsx, the production digest, and the reset function.