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

AndryAndry Dina
2
Next.js 15 View Transitions API Tutorial

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

bash
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:

javascript
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:

jsx
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:

jsx
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:

jsx
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:

css
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:

css
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:

css
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:

css
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:

css
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:

jsx
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.

Similar articles

Never miss an update

Subscribe to receive news and special offers.

By subscribing you agree to our Privacy Policy.