Smooth Page Transitions in Next.js with next-view-transitions
4 days ago
3
The next-view-transitions package brings smooth page transitions to Next.js. We'll build a simple blog and add smooth element transitions using viewTransitionName properties to make titles and dates morph between pages. Here's a demo of what we'll be building:
Finally got around to writing a tutorial explaining next-view-transitions! At the end of the (5 minute) tutorial, you'll have an app that looks like this. Thanks @shuding_ for the great package!
export interface Post { id: number; slug: string; title: string; description: string; date: string; content: string;}export const posts: Post[] = [ { id: 1, slug: "getting-started-nextjs", title: "Getting Started with Next.js", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.", date: "2024-12-01", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.", }, { id: 2, slug: "react-best-practices", title: "React Best Practices", description: "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.", date: "2024-12-05", content: "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore.", }, { id: 3, slug: "tailwind-tips", title: "Tailwind CSS Tips and Tricks", description: "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", date: "2024-12-10", content: "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt.", },];
import { posts } from "@/lib/posts";import { Badge } from "@/components/ui/badge";import Link from "next/link";import { notFound } from "next/navigation";interface PostPageProps { params: Promise<{ slug: string }>;}export default async function PostPage({ params }: PostPageProps) { const { slug } = await params; const post = posts.find((p) => p.slug === slug); if (!post) { notFound(); } return ( <div className="container mx-auto px-4 py-8"> <Link href="/" className="text-blue-600 hover:underline mb-4 inline-block" > ← Back to posts </Link> <article className="max-w-3xl"> <header className="mb-8"> <h1 className="text-4xl font-bold mb-4">{post.title}</h1> <div className="flex items-center gap-4"> <Badge>{post.date}</Badge> </div> </header> <div className="prose prose-lg"> <p>{post.content}</p> </div> </article> </div> );}export function generateStaticParams() { return posts.map((post) => ({ slug: post.slug, }));}
You now have a working blog with normal page navigation.
Wrap your app in app/layout.tsx:
import { ViewTransitions } from "next-view-transitions";import "./globals.css";import type { ReactNode } from "react";interface RootLayoutProps { children: ReactNode;}export default function RootLayout({ children }: RootLayoutProps) { return ( <ViewTransitions> <html lang="en"> <body>{children}</body> </html> </ViewTransitions> );}
Replace all next/link imports with the transition-enabled version:
// Instead of:import Link from "next/link";// Use:import { Link } from "next-view-transitions";
At this point, navigation looks identical to before. The package doesn't add any transitions by default.
Add viewTransitionName properties to elements that should morph between pages:
// In your posts listing (app/page.tsx):<CardTitle className="line-clamp-2" style={{ viewTransitionName: `title-${post.slug}` }}> {post.title}</CardTitle><Badge variant="secondary" style={{ viewTransitionName: `date-${post.slug}` }}> {post.date}</Badge>
// In your individual post page (app/posts/[slug]/page.tsx):<h1 className="text-4xl font-bold mb-4" style={{ viewTransitionName: `title-${post.slug}` }}> {post.title}</h1><Badge style={{ viewTransitionName: `date-${post.slug}` }}> {post.date}</Badge>
Now the title and date smoothly morph from the card to the post page. This is the only way to actually see transitions - matching viewTransitionName values between pages.
Done
These transitions provide visual continuity, make the app feel more responsive, and help users maintain context. The View Transitions API is supported in Chrome, Edge, and Opera, with graceful fallback to normal navigation in other browsers.