Smooth Page Transitions in Next.js 15 – A Deep Dive Guide

I've been building Next.js apps for years, and I've always struggled with page transitions. Framer Motion felt heavy. Custom solutions broke constantly. CSS animations never felt quite right.
Then Next.js 15 dropped the View Transitions API support, and everything changed.
What Makes This Different
The View Transitions API isn't another JavaScript animation library. It's a browser-native feature that handles transitions at the rendering level. Think hardware acceleration, minimal bundle size, and buttery smooth animations that don't block your UI.
The best part? Next.js made it ridiculously simple to implement.
Getting Started: 5-Minute Setup
Step 1: Install the Package
1# Choose your package manager
2npm install next-view-transitions
3yarn add next-view-transitions
4pnpm add next-view-transitions
Step 2: Enable View Transitions
Update your next.config.js
:
1/** @type {import('next').NextConfig} */
2const nextConfig = {
3 experimental: {
4 viewTransition: true,
5 },
6};
7
8module.exports = nextConfig;
Step 3: Wrap Your App
In your root layout, add the ViewTransitions provider:
1import { ViewTransitions } from 'next-view-transitions'
2
3export default function RootLayout({ children }) {
4 return (
5 <ViewTransitions>
6 <html lang="en">
7 <body className="font-sans">
8 {children}
9 </body>
10 </html>
11 </ViewTransitions>
12 )
13}
Step 4: Use the Enhanced Navigation Components
Now you need to use the special Link component and router hook for transitions to actually work. For links, replace Next.js's default Link with the transition-enabled version:
1import { Link } from 'next-view-transitions'
2
3export default function Navigation() {
4 return (
5 <nav className="flex space-x-4">
6 <Link href="/about" className="hover:text-blue-600">
7 About
8 </Link>
9 <Link href="/blog" className="hover:text-blue-600">
10 Blog
11 </Link>
12 <Link href="/contact" className="hover:text-blue-600">
13 Contact
14 </Link>
15 </nav>
16 )
17}
For programmatic navigation, use the transition router hook:
1import { useTransitionRouter } from 'next-view-transitions'
2
3export default function BlogPost() {
4 const router = useTransitionRouter()
5
6 const handleNavigation = () => {
7 // All Next.js router methods work with transitions
8 router.push('/blog/next-post')
9 // router.replace('/dashboard')
10 // router.back()
11 }
12
13 return (
14 <div className="prose max-w-none">
15 <h1>My Blog Post</h1>
16 <p>Content here...</p>
17
18 <button
19 onClick={handleNavigation}
20 className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
21 >
22 Read Next Post
23 </button>
24 </div>
25 )
26}
That's it. You now have basic view transitions working.
Creating Beautiful Transitions with Tailwind
Fade Transitions
The simplest transition is a fade. Add this CSS to your globals:
1::view-transition-old(root),
2::view-transition-new(root) {
3 animation-duration: 300ms;
4 animation-timing-function: ease-in-out;
5}
6
7::view-transition-old(root) {
8 animation-name: fade-out;
9}
10
11::view-transition-new(root) {
12 animation-name: fade-in;
13}
14
15@keyframes fade-out {
16 to { opacity: 0; }
17}
18
19@keyframes fade-in {
20 from { opacity: 0; }
21}
Slide Transitions
Want something more dynamic? Try slide transitions:
1::view-transition-old(root) {
2 animation: slide-out-left 350ms ease-in-out;
3}
4
5::view-transition-new(root) {
6 animation: slide-in-right 350ms ease-in-out;
7}
8
9@keyframes slide-out-left {
10 to {
11 transform: translateX(-100%);
12 }
13}
14
15@keyframes slide-in-right {
16 from {
17 transform: translateX(100%);
18 }
19}
Scale Transitions
For a more modern feel, try scale transitions:
1::view-transition-old(root) {
2 animation: scale-down 300ms ease-in-out;
3}
4
5::view-transition-new(root) {
6 animation: scale-up 300ms ease-in-out;
7}
8
9@keyframes scale-down {
10 to {
11 transform: scale(0.95);
12 opacity: 0;
13 }
14}
15
16@keyframes scale-up {
17 from {
18 transform: scale(1.05);
19 opacity: 0;
20 }
21}
Advanced Techniques
Conditional Transitions
You can create different transitions for different routes using CSS selectors:
1/* Different transition for blog posts */
2[data-route*="/blog"] ::view-transition-old(root) {
3 animation: slide-up 400ms ease-out;
4}
5
6[data-route*="/blog"] ::view-transition-new(root) {
7 animation: slide-down 400ms ease-out;
8}
Element-Specific Transitions
Target specific elements for more granular control:
1/* Transition specific components */
2::view-transition-old(header),
3::view-transition-new(header) {
4 animation-duration: 200ms;
5}
6
7::view-transition-old(main),
8::view-transition-new(main) {
9 animation-duration: 400ms;
10}
Performance Benefits
Here's why this approach beats traditional solutions:
Bundle Size: Zero JavaScript for animations means smaller bundles Performance: Hardware-accelerated transitions that don't block rendering Compatibility: Graceful degradation for browsers without support Simplicity: No complex state management or animation libraries
Browser Support and Fallbacks
The View Transitions API works in Chrome 111+, Edge 111+, and other Chromium browsers. For Firefox and Safari, the transitions simply won't occur - users get instant navigation instead.
This graceful degradation means you can implement this today without worrying about browser compatibility.
Real-World Example
Here's a complete example of a blog with smooth transitions:
1// app/layout.js
2import { ViewTransitions } from 'next-view-transitions'
3import './globals.css'
4
5export default function RootLayout({ children }) {
6 return (
7 <ViewTransitions>
8 <html lang="en">
9 <body className="bg-white text-gray-900">
10 <nav className="border-b border-gray-200 p-4">
11 <div className="max-w-4xl mx-auto flex justify-between items-center">
12 <h1 className="text-xl font-bold">My Blog</h1>
13 <div className="space-x-4">
14 <a href="/" className="hover:text-blue-600">Home</a>
15 <a href="/blog" className="hover:text-blue-600">Blog</a>
16 <a href="/about" className="hover:text-blue-600">About</a>
17 </div>
18 </div>
19 </nav>
20 <main className="max-w-4xl mx-auto p-4">
21 {children}
22 </main>
23 </body>
24 </html>
25 </ViewTransitions>
26 )
27}
With the CSS transitions from earlier, every navigation feels smooth and intentional.
What's Next
View Transitions are still experimental in Next.js, but they're stable enough for production use. As browser support improves, we'll see more advanced features like:
- Shared element transitions
- Custom transition names
- Better developer tools
Why This Matters
Good animations aren't just eye candy. They provide visual continuity that helps users understand your app's navigation flow. When done right, they make your app feel faster and more responsive.
The View Transitions API gives us that polish without the complexity or performance cost of traditional animation libraries.
Want more Next.js tips and tutorials? Subscribe to my newsletter for weekly deep dives into modern web development.