View Transitions in Next.js
`` works out of the box for `startTransition`/`Suspense` updates. To also animate `` navigations:
Overview
View Transitions in Next.js
Setup
works out of the box for `startTransition`/`Suspense` updates. To also animate navigations:
// next.config.js
const nextConfig = {
experimental: { viewTransition: true },
};
module.exports = nextConfig;
This wraps every `` navigation in document.startViewTransition. Any VT with default="auto" fires on every link click — use default="none" to prevent competing animations.
Requires react@canary: npm install react@canary react-dom@canary
Next.js Implementation Additions
When following implementation.md, apply these additions:
After Step 2: Enable the experimental flag above.
Step 4: Use transitionTypes on `` instead of manual addTransitionType:
View
Back
Reserve startTransition + addTransitionType for programmatic navigation (buttons, forms).
After Step 6: For same-route dynamic segments (e.g., /collection/[slug]), use the key + name + share pattern — see Same-Route Dynamic Segment Transitions below.
Layout-Level ViewTransition
Do NOT add a layout-level VT wrapping {children} if pages have their own VTs. Both fire simultaneously, producing competing animations.
A bare `` in layout works only if pages have no VTs of their own. Once any page adds a VT, use default="none" on the layout VT or remove it.
Layouts persist across navigations — enter/exit only fire on initial mount, not on route changes. Don't use type-keyed maps in layouts.
// Prevents layout from interfering with per-page VTs
{children}
The transitionTypes Prop on next/link
Native prop — no wrapper component needed, works in Server Components:
View Product
Replaces the manual pattern of onNavigate + startTransition + addTransitionType + router.push(). Reserve manual startTransition for non-link interactions (buttons, forms).
Programmatic Navigation
'use client';
function handleNavigate(href: string) {
const router = useRouter();
startTransition(() => {
addTransitionType('nav-forward');
router.push(href);
});
}
Directional Navigation
Place type-keyed VTs in page components (not layouts):
Two-Layer Pattern
Directional slides + Suspense reveals coexist because they fire at different moments:
<div>
}>
</div>
Shared Elements Across Routes
// List page
{products.map((product) => (
))}
// Detail page — same name
Same-Route Dynamic Segment Transitions
When navigating between dynamic segments of the same route (e.g., /collection/[slug]), the page stays mounted — enter/exit never fire. Use key + name + share:
}>
key={slug}forces unmount/remount on changename+share="auto"creates a shared element crossfade- VT inside `` (without keying Suspense) keeps old content visible during loading
Suspense and Loading States
}>
Don't combine with a layout-level VT using default="auto".
Server Components
- `` works in both Server and Client Components
- `` works in Server Components — no
'use client'needed addTransitionTypeandstartTransitionfor programmatic nav require Client Components