View Transitions API avec Next.js App Router : animations de pages fluides sans bibliothèques

Les transitions de pages ont toujours été parmi les choses les plus difficiles à réussir dans les applications web. Les approches traditionnelles nécessitent des bibliothèques d'animation lourdes, une gestion d'état complexe et une orchestration minutieuse des éléments entrants et sortants. La View Transitions API change tout en donnant aux navigateurs un support natif pour des transitions fluides entre les états de page.
Dans ce tutoriel, vous allez construire une application Next.js App Router avec des transitions de pages parfaitement fluides en utilisant uniquement la View Transitions API et CSS. Pas de Framer Motion, pas de GSAP, pas de bibliothèques d'animation tierces — juste la plateforme.
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 18+ installé (Node 20 recommandé)
- npm ou pnpm comme gestionnaire de paquets
- Des connaissances intermédiaires en React et Next.js App Router
- Une compréhension basique des animations et transitions CSS
- Un navigateur moderne (Chrome 111+, Edge 111+, Safari 18+, Firefox 126+)
Ce que vous allez construire
À la fin de ce tutoriel, vous aurez :
- Une application Next.js avec des transitions de fondu fluides entre les pages
- Des animations de morphing qui connectent les éléments partagés entre les routes
- Des transitions de glissement directionnelles personnalisées selon la direction de navigation
- Un hook de transition réutilisable que vous pouvez intégrer dans tout projet Next.js
Comprendre la View Transitions API
La View Transitions API fonctionne en capturant une capture d'écran de l'état actuel de la page, en effectuant la mise à jour du DOM, puis en animant entre les anciens et nouveaux états. Le navigateur gère tout le travail lourd — vous lui dites simplement quoi transitionner.
Voici le concept de base :
// L'API de base
document.startViewTransition(() => {
// Mettez à jour le DOM ici
updateThePage();
});Quand vous appelez startViewTransition, le navigateur :
- Capture l'état visuel actuel comme une capture d'écran
- Exécute votre callback pour mettre à jour le DOM
- Capture le nouvel état visuel
- Crée des pseudo-éléments (
::view-transition-oldet::view-transition-new) - Anime entre l'ancien et le nouveau en utilisant des animations CSS
La magie est que vous contrôlez entièrement l'animation avec CSS.
Pseudo-éléments de View Transition
Le navigateur crée un arbre de pseudo-éléments pendant une transition :
::view-transition
├── ::view-transition-group(root)
│ └── ::view-transition-image-pair(root)
│ ├── ::view-transition-old(root)
│ └── ::view-transition-new(root)
├── ::view-transition-group(header)
│ └── ::view-transition-image-pair(header)
│ ├── ::view-transition-old(header)
│ └── ::view-transition-new(header)
Chaque groupe de transition nommé obtient son propre ensemble de pseudo-éléments, que vous pouvez animer indépendamment.
Étape 1 : Configuration du projet
Créez un nouveau projet Next.js avec App Router :
npx create-next-app@latest view-transitions-demo \
--typescript \
--tailwind \
--app \
--src-dir \
--import-alias "@/*"
cd view-transitions-demoInstallez la seule dépendance supplémentaire — un petit utilitaire pour les noms de classes conditionnels :
npm install clsxC'est tout. Aucune bibliothèque d'animation.
Étape 2 : Activer les View Transitions dans Next.js
Next.js n'active pas les View Transitions par défaut. Vous devez opter pour cette fonctionnalité via le fichier next.config.ts :
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
viewTransition: true,
},
};
export default nextConfig;Ce flag indique à Next.js d'envelopper les navigations de routes dans document.startViewTransition() automatiquement. Sans lui, les navigations se produisent instantanément sans transition.
Étape 3 : Créer le layout de base
Configurez un layout avec une barre de navigation qui persiste entre les pages :
// src/app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import Link from "next/link";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "View Transitions Demo",
description: "Transitions de pages fluides avec la View Transitions API",
};
const navItems = [
{ href: "/", label: "Accueil" },
{ href: "/about", label: "À propos" },
{ href: "/projects", label: "Projets" },
{ href: "/blog", label: "Blog" },
];
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="fr">
<body className={inter.className}>
<nav className="sticky top-0 z-50 border-b border-gray-200 bg-white/80 backdrop-blur-sm">
<div className="mx-auto flex max-w-5xl items-center justify-between px-6 py-4">
<Link
href="/"
className="text-xl font-bold text-gray-900"
style={{ viewTransitionName: "site-logo" }}
>
VT Demo
</Link>
<ul className="flex gap-6">
{navItems.map((item) => (
<li key={item.href}>
<Link
href={item.href}
className="text-gray-600 transition-colors hover:text-gray-900"
>
{item.label}
</Link>
</li>
))}
</ul>
</div>
</nav>
<main className="mx-auto max-w-5xl px-6 py-12">{children}</main>
</body>
</html>
);
}Remarquez le viewTransitionName sur le logo. C'est la propriété CSS clé qui indique au navigateur de faire un morphing de cet élément entre les pages au lieu de faire un fondu de la page entière.
Étape 4 : Ajouter les styles globaux de View Transition
Ajoutez les styles de transition de base à votre CSS global :
/* src/app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Transition de fondu par défaut pour toute la page */
::view-transition-old(root) {
animation: fade-out 0.3s ease-in-out;
}
::view-transition-new(root) {
animation: fade-in 0.3s ease-in-out;
}
@keyframes fade-out {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.98);
}
}
@keyframes fade-in {
from {
opacity: 0;
transform: scale(1.02);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* Animation de morphing pour les éléments nommés (comme le logo) */
::view-transition-group(site-logo) {
animation-duration: 0.3s;
animation-timing-function: ease-in-out;
}
/* Empêcher le décalage de layout pendant les transitions */
::view-transition {
pointer-events: none;
}Avec juste ce CSS, chaque navigation de page a maintenant un fondu fluide avec un subtil effet de zoom. Le logo se transforme en douceur car il possède un viewTransitionName.
Étape 5 : Construire des pages avec des éléments partagés
Créez la page d'accueil avec des cartes de projets qui se transformeront en leurs pages de détails :
// src/app/page.tsx
import Link from "next/link";
const projects = [
{
id: "aurora",
title: "Aurora",
description: "Une plateforme de collaboration en temps réel construite avec WebSockets",
color: "bg-gradient-to-br from-purple-500 to-pink-500",
tags: ["React", "WebSocket", "Redis"],
},
{
id: "nebula",
title: "Nebula",
description: "Pipeline de déploiement cloud-native avec zéro temps d'arrêt",
color: "bg-gradient-to-br from-blue-500 to-cyan-500",
tags: ["Docker", "Kubernetes", "Go"],
},
{
id: "prism",
title: "Prism",
description: "Outil de revue de code propulsé par l'IA qui détecte les bugs avant expédition",
color: "bg-gradient-to-br from-amber-500 to-orange-500",
tags: ["Python", "ML", "TypeScript"],
},
];
export default function HomePage() {
return (
<div>
<h1
className="mb-4 text-5xl font-bold tracking-tight text-gray-900"
style={{ viewTransitionName: "page-title" }}
>
Bienvenue
</h1>
<p className="mb-12 text-xl text-gray-600">
Explorez nos derniers projets avec des transitions fluides.
</p>
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{projects.map((project) => (
<Link
key={project.id}
href={`/projects/${project.id}`}
className="group block"
>
<div
className={`${project.color} mb-4 h-48 rounded-2xl transition-shadow group-hover:shadow-xl`}
style={{
viewTransitionName: `project-image-${project.id}`,
}}
/>
<h2
className="mb-2 text-xl font-semibold text-gray-900"
style={{
viewTransitionName: `project-title-${project.id}`,
}}
>
{project.title}
</h2>
<p className="text-gray-600">{project.description}</p>
<div className="mt-3 flex gap-2">
{project.tags.map((tag) => (
<span
key={tag}
className="rounded-full bg-gray-100 px-3 py-1 text-xs font-medium text-gray-700"
>
{tag}
</span>
))}
</div>
</Link>
))}
</div>
</div>
);
}Chaque carte de projet a des valeurs viewTransitionName uniques pour son image et son titre. Ces noms doivent correspondre exactement sur la page de détail pour que le morphing fonctionne.
Étape 6 : Créer la page de détail du projet avec des transitions de morphing
// src/app/projects/[id]/page.tsx
import Link from "next/link";
import { notFound } from "next/navigation";
const projects: Record<
string,
{
title: string;
description: string;
color: string;
tags: string[];
content: string;
}
> = {
aurora: {
title: "Aurora",
description: "Une plateforme de collaboration en temps réel construite avec WebSockets",
color: "bg-gradient-to-br from-purple-500 to-pink-500",
tags: ["React", "WebSocket", "Redis"],
content:
"Aurora permet aux équipes de collaborer en temps réel avec une latence inférieure à 50 ms. Construite sur une infrastructure WebSocket avec Redis pub/sub pour la mise à l'échelle horizontale, elle prend en charge les curseurs en direct, l'édition simultanée et la détection de présence dans les équipes distribuées.",
},
nebula: {
title: "Nebula",
description: "Pipeline de déploiement cloud-native avec zéro temps d'arrêt",
color: "bg-gradient-to-br from-blue-500 to-cyan-500",
tags: ["Docker", "Kubernetes", "Go"],
content:
"Nebula automatise tout le cycle de déploiement, du push de code à la production. Avec des déploiements canari intégrés, des rollbacks automatiques et des vérifications de santé complètes, il garantit que vos services restent disponibles pendant chaque release.",
},
prism: {
title: "Prism",
description: "Outil de revue de code propulsé par l'IA qui détecte les bugs avant expédition",
color: "bg-gradient-to-br from-amber-500 to-orange-500",
tags: ["Python", "ML", "TypeScript"],
content:
"Prism utilise des modèles de machine learning entraînés sur des millions de revues de code pour identifier les bugs potentiels, les vulnérabilités de sécurité et les problèmes de performance avant qu'ils n'atteignent la production. Il s'intègre directement dans votre pipeline CI.",
},
};
export default async function ProjectPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const project = projects[id];
if (!project) notFound();
return (
<div>
<Link
href="/"
className="mb-8 inline-flex items-center text-gray-500 hover:text-gray-900"
>
← Retour aux projets
</Link>
<div
className={`${project.color} mb-8 h-64 rounded-2xl md:h-80`}
style={{ viewTransitionName: `project-image-${id}` }}
/>
<h1
className="mb-4 text-4xl font-bold text-gray-900"
style={{ viewTransitionName: `project-title-${id}` }}
>
{project.title}
</h1>
<div className="mb-6 flex gap-2">
{project.tags.map((tag) => (
<span
key={tag}
className="rounded-full bg-gray-100 px-4 py-1.5 text-sm font-medium text-gray-700"
>
{tag}
</span>
))}
</div>
<p className="text-lg leading-relaxed text-gray-700">
{project.content}
</p>
</div>
);
}Quand vous cliquez sur une carte de projet sur la page d'accueil, la boîte de dégradé se transforme en douceur de sa taille de carte à la largeur complète du hero. Le titre glisse vers sa nouvelle position. Tout le reste fait un fondu. Tout cela se produit sans aucun code JavaScript d'animation.
Étape 7 : Construire un hook de transition personnalisé
Pour plus de contrôle sur les transitions, créez un hook personnalisé qui vous permet de déclencher des transitions programmatiquement :
// src/hooks/use-view-transition.ts
"use client";
import { useCallback, useRef } from "react";
type TransitionCallback = () => void | Promise<void>;
interface ViewTransitionOptions {
onStart?: () => void;
onFinish?: () => void;
onError?: (error: unknown) => void;
}
export function useViewTransition(options: ViewTransitionOptions = {}) {
const transitionRef = useRef<ViewTransition | null>(null);
const startTransition = useCallback(
async (callback: TransitionCallback) => {
// Fallback pour les navigateurs sans support View Transitions
if (!document.startViewTransition) {
await callback();
return;
}
options.onStart?.();
try {
const transition = document.startViewTransition(async () => {
await callback();
});
transitionRef.current = transition;
await transition.finished;
options.onFinish?.();
} catch (error) {
options.onError?.(error);
} finally {
transitionRef.current = null;
}
},
[options]
);
const skipTransition = useCallback(() => {
transitionRef.current?.skipTransition();
}, []);
return { startTransition, skipTransition };
}Utilisez ce hook pour les changements d'état côté client qui doivent s'animer :
// Exemple : commutateur d'onglets animé
"use client";
import { useState } from "react";
import { useViewTransition } from "@/hooks/use-view-transition";
export function AnimatedTabs() {
const [activeTab, setActiveTab] = useState(0);
const { startTransition } = useViewTransition();
const tabs = ["Vue d'ensemble", "Fonctionnalités", "Tarifs"];
const switchTab = (index: number) => {
startTransition(() => {
setActiveTab(index);
});
};
return (
<div>
<div className="flex gap-1 rounded-lg bg-gray-100 p-1">
{tabs.map((tab, i) => (
<button
key={tab}
onClick={() => switchTab(i)}
className="relative rounded-md px-4 py-2 text-sm font-medium"
>
{activeTab === i && (
<span
className="absolute inset-0 rounded-md bg-white shadow-sm"
style={{ viewTransitionName: "active-tab" }}
/>
)}
<span className="relative z-10">{tab}</span>
</button>
))}
</div>
<div
className="mt-6"
style={{ viewTransitionName: "tab-content" }}
>
<p>Contenu pour {tabs[activeTab]}</p>
</div>
</div>
);
}L'indicateur d'onglet actif se transforme en douceur entre les positions, et le contenu fait un fondu — le tout piloté par CSS.
Étape 8 : Transitions de glissement directionnelles
Un pattern courant est de faire glisser les pages dans différentes directions selon la navigation. Vous pouvez y parvenir en ajoutant des classes CSS qui contrôlent la direction de l'animation :
/* src/app/globals.css — ajoutez ceci */
/* Transition glissement gauche (navigation en avant) */
.slide-forward::view-transition-old(root) {
animation: slide-out-left 0.3s ease-in-out;
}
.slide-forward::view-transition-new(root) {
animation: slide-in-right 0.3s ease-in-out;
}
/* Transition glissement droite (navigation en arrière) */
.slide-back::view-transition-old(root) {
animation: slide-out-right 0.3s ease-in-out;
}
.slide-back::view-transition-new(root) {
animation: slide-in-left 0.3s ease-in-out;
}
@keyframes slide-out-left {
to {
opacity: 0;
transform: translateX(-30px);
}
}
@keyframes slide-in-right {
from {
opacity: 0;
transform: translateX(30px);
}
}
@keyframes slide-out-right {
to {
opacity: 0;
transform: translateX(30px);
}
}
@keyframes slide-in-left {
from {
opacity: 0;
transform: translateX(-30px);
}
}Créez un composant de lien qui définit la classe de direction :
// src/components/directional-link.tsx
"use client";
import Link from "next/link";
import { type ComponentProps, useCallback } from "react";
type Direction = "forward" | "back";
interface DirectionalLinkProps extends ComponentProps<typeof Link> {
direction?: Direction;
}
export function DirectionalLink({
direction = "forward",
onClick,
...props
}: DirectionalLinkProps) {
const handleClick = useCallback(
(e: React.MouseEvent<HTMLAnchorElement>) => {
document.documentElement.classList.remove(
"slide-forward",
"slide-back"
);
document.documentElement.classList.add(
direction === "forward" ? "slide-forward" : "slide-back"
);
onClick?.(e);
},
[direction, onClick]
);
return <Link {...props} onClick={handleClick} />;
}Utilisez maintenant DirectionalLink dans vos pages :
// Sur le bouton retour de la page de détail
<DirectionalLink href="/" direction="back">
← Retour aux projets
</DirectionalLink>
// Sur les cartes de projets de la page d'accueil
<DirectionalLink href={`/projects/${project.id}`} direction="forward">
Voir le projet →
</DirectionalLink>Étape 9 : Groupes de transition pour des animations indépendantes
Parfois vous voulez que différentes parties de la page s'animent indépendamment. Les noms de View Transition vous permettent de créer des groupes d'animation séparés :
// src/app/blog/page.tsx
const posts = [
{ id: 1, title: "Débuter avec Rust", date: "2026-03-15" },
{ id: 2, title: "Pourquoi TypeScript 6 compte", date: "2026-03-10" },
{ id: 3, title: "Edge Computing en 2026", date: "2026-03-05" },
];
export default function BlogPage() {
return (
<div>
<h1
className="mb-8 text-4xl font-bold"
style={{ viewTransitionName: "page-title" }}
>
Blog
</h1>
<aside
className="mb-8 rounded-xl bg-blue-50 p-6"
style={{ viewTransitionName: "blog-sidebar" }}
>
<p className="text-blue-800">Abonnez-vous pour recevoir les nouveaux articles.</p>
</aside>
<div className="space-y-6">
{posts.map((post) => (
<article
key={post.id}
className="rounded-xl border p-6"
style={{
viewTransitionName: `blog-post-${post.id}`,
}}
>
<time className="text-sm text-gray-500">{post.date}</time>
<h2 className="mt-1 text-xl font-semibold">{post.title}</h2>
</article>
))}
</div>
</div>
);
}Ajoutez des animations échelonnées pour les articles :
/* src/app/globals.css — ajoutez ceci */
/* Entrée échelonnée pour les articles du blog */
::view-transition-new(blog-post-1) {
animation: fade-in 0.3s ease-out 0.05s both;
}
::view-transition-new(blog-post-2) {
animation: fade-in 0.3s ease-out 0.1s both;
}
::view-transition-new(blog-post-3) {
animation: fade-in 0.3s ease-out 0.15s both;
}
/* La sidebar glisse depuis le côté */
::view-transition-new(blog-sidebar) {
animation: slide-in-left 0.4s ease-out;
}
::view-transition-old(blog-sidebar) {
animation: slide-out-left 0.3s ease-in;
}Chaque élément nommé transitionne indépendamment — la sidebar glisse, les articles entrent en échelons, et le titre de page se transforme. Tout déclaratif en CSS, aucune logique d'animation JavaScript.
Étape 10 : Gérer les préférences de mouvement réduit
Respectez toujours les utilisateurs qui préfèrent réduire les animations. La View Transitions API rend cela simple :
/* src/app/globals.css — ajoutez en haut */
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}Cette seule règle désactive toutes les animations de view transition pour les utilisateurs qui ont activé « Réduire les animations » dans les paramètres de leur OS. La mise à jour du DOM se produit toujours — seule l'animation est supprimée.
Gérez également cela dans votre hook personnalisé :
// use-view-transition.ts mis à jour
export function useViewTransition(options: ViewTransitionOptions = {}) {
const prefersReducedMotion =
typeof window !== "undefined" &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const startTransition = useCallback(
async (callback: TransitionCallback) => {
if (!document.startViewTransition || prefersReducedMotion) {
await callback();
return;
}
// ... reste de l'implémentation
},
[options, prefersReducedMotion]
);
// ...
}Étape 11 : Animations de morphing avancées pour les galeries d'images
L'une des utilisations les plus impressionnantes des View Transitions est les galeries d'images où les vignettes se transforment en vue plein écran :
// src/app/about/page.tsx
const team = [
{ id: "sarah", name: "Sarah Chen", role: "Responsable ingénierie" },
{ id: "omar", name: "Omar Khalil", role: "Directeur design" },
{ id: "alex", name: "Alex Rivera", role: "Chef de produit" },
];
export default function AboutPage() {
return (
<div>
<h1
className="mb-4 text-4xl font-bold"
style={{ viewTransitionName: "page-title" }}
>
À propos
</h1>
<p className="mb-12 text-lg text-gray-600">Découvrez l'équipe derrière tout cela.</p>
<div className="grid gap-8 md:grid-cols-3">
{team.map((member) => (
<div key={member.id} className="text-center">
<div
className="mx-auto mb-4 h-32 w-32 rounded-full bg-gradient-to-br from-gray-300 to-gray-400"
style={{
viewTransitionName: `avatar-${member.id}`,
}}
/>
<h3
className="font-semibold"
style={{
viewTransitionName: `name-${member.id}`,
}}
>
{member.name}
</h3>
<p className="text-sm text-gray-500">{member.role}</p>
</div>
))}
</div>
</div>
);
}Ajoutez du CSS pour un morphing fluide des avatars :
/* Morphing fluide pour les images d'avatar */
::view-transition-group(avatar-sarah),
::view-transition-group(avatar-omar),
::view-transition-group(avatar-alex) {
animation-duration: 0.4s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}Lors de la navigation entre des pages qui partagent le même viewTransitionName, le navigateur calcule la différence de position et de taille et interpole en douceur entre les deux.
Étape 12 : Tests et débogage des transitions
Chrome DevTools
Chrome DevTools dispose d'un support intégré pour déboguer les View Transitions :
- Ouvrez DevTools et allez au panneau Animations
- Naviguez entre les pages — chaque transition apparaît dans la timeline
- Vous pouvez ralentir, rejouer et inspecter les animations individuelles
- Le panneau Elements affiche les pseudo-éléments
::view-transitionpendant une transition
Ralentir les transitions pour le développement
Ajoutez une règle CSS temporaire pour tout ralentir pendant le développement :
/* DÉVELOPPEMENT UNIQUEMENT — supprimez avant la production */
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation-duration: 2s !important;
}Problèmes de débogage courants
Problème : Les éléments clignotent au lieu de se transformer
Cela se produit quand les valeurs viewTransitionName ne correspondent pas exactement entre les anciennes et nouvelles pages.
Problème : Plusieurs éléments avec le même nom de transition
Chaque viewTransitionName doit être unique sur la page à tout moment. Si deux éléments partagent un nom, la transition échoue silencieusement :
// Ceci va échouer — deux éléments avec le même nom
{items.map((item) => (
<div style={{ viewTransitionName: "card" }}>
{item.title}
</div>
))}
// Ceci fonctionne — noms uniques
{items.map((item) => (
<div style={{ viewTransitionName: `card-${item.id}` }}>
{item.title}
</div>
))}Problème : Les transitions ne se déclenchent pas
Assurez-vous que le flag experimental.viewTransition est défini dans next.config.ts. Vérifiez également que vous utilisez next/link pour la navigation — les rechargements de page complets contournent les View Transitions.
Considérations de performance
Les View Transitions sont remarquablement performantes car :
- Accélération GPU : Le navigateur compose l'animation sur le GPU
- Pas de layout thrashing : Les animations s'exécutent sur des captures, pas sur le DOM en direct
- Optimisation automatique : Le navigateur ne capture que ce dont il a besoin
Gardez cependant ces conseils en tête :
- Limitez les transitions nommées : Chaque élément nommé ajoute une étape de capture. Restez en dessous de 10 à 15 éléments nommés par page
- Évitez les grandes images : Le navigateur capture les éléments à leur taille affichée
- Utilisez
will-changeavec parcimonie : Le navigateur optimise déjà les View Transitions automatiquement - Gardez des durées courtes : 200-400 ms semble réactif. Au-delà de 500 ms, cela semble lent
Support navigateur et amélioration progressive
En 2026, les View Transitions sont supportées dans :
| Navigateur | Version | Support |
|---|---|---|
| Chrome | 111+ | Complet |
| Edge | 111+ | Complet |
| Safari | 18+ | Complet |
| Firefox | 126+ | Complet |
Pour les navigateurs plus anciens, la transition ne se joue simplement pas — la navigation se produit instantanément. Votre application fonctionne parfaitement dans les deux cas. C'est de la véritable amélioration progressive.
Dépannage
Interruption de transition
Si un utilisateur clique sur un lien pendant qu'une transition est encore en cours, la transition actuelle est sautée et la nouvelle commence. C'est le comportement correct du navigateur.
Composants Server et View Transitions
Les View Transitions fonctionnent avec les composants Server et Client dans Next.js. La propriété CSS viewTransitionName peut être définie en inline sur tout composant. Le hook useViewTransition ne fonctionne que dans les composants Client, mais vous en avez rarement besoin pour les transitions basées sur les routes.
Noms de transition dynamiques
Quand vous générez des viewTransitionName dynamiquement, assurez-vous que la valeur est un CSS custom-ident valide. Évitez les noms commençant par des chiffres :
// Mauvais — commence par un chiffre
style={{ viewTransitionName: `123-card` }}
// Bon — commence par une lettre
style={{ viewTransitionName: `card-123` }}Prochaines étapes
Maintenant que vous avez des transitions de pages fluides, voici comment aller plus loin :
- Combiner avec les Server Actions : Utilisez les View Transitions pour des mises à jour optimistes de l'UI lors de la soumission de formulaires
- Construire une animation de layout partagé : Créez une sidebar persistante qui transforme son indicateur actif
- Ajouter des transitions spécifiques par page : Utilisez CSS
view-transition-namesur lebodyavec différentes classes par route - Explorer les transitions cross-document : La spécification Level 2 à venir supporte les transitions entre navigations de pages complètes (MPA)
Conclusion
La View Transitions API élimine le besoin de bibliothèques d'animation complexes dans la plupart des scénarios de navigation. Avec l'intégration Next.js App Router, vous obtenez des fondus fluides automatiquement, et avec quelques déclarations viewTransitionName, vous obtenez des animations de morphing impressionnantes qui semblent natives.
Les points clés à retenir :
- Activez
experimental.viewTransitiondansnext.config.tspour le support automatique des transitions de routes - Utilisez la propriété CSS
viewTransitionNamepour créer des animations de morphing entre éléments partagés - Contrôlez les animations entièrement avec les pseudo-éléments CSS — pas de JavaScript nécessaire
- Respectez toujours
prefers-reduced-motionpour l'accessibilité - Les View Transitions sont de l'amélioration progressive — les applications fonctionnent parfaitement sans elles
La meilleure bibliothèque d'animation est celle que vous n'avez pas besoin d'installer.
Discutez de votre projet avec nous
Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.
Trouvons les meilleures solutions pour vos besoins.
Articles connexes

Motion pour React : Créer des animations, gestes et transitions de qualité production
Maîtrisez la bibliothèque Motion (anciennement Framer Motion) pour React. Apprenez à créer des animations fluides, des gestes interactifs, des transitions de mise en page, des effets de défilement et des animations de sortie avec TypeScript.

Zustand + Next.js App Router : Gestion d'État React Moderne du Zéro à la Production
Maîtrisez la gestion d'état React moderne avec Zustand et Next.js 15 App Router. Ce tutoriel pratique couvre la création de stores, les middleware, la persistance, l'hydratation côté serveur et les patterns concrets pour des applications évolutives.

Construire une application full-stack en temps réel avec Convex et Next.js 15
Apprenez à construire une application full-stack en temps réel avec Convex et Next.js 15. Ce tutoriel couvre la conception de schémas, les requêtes, les mutations, les abonnements en temps réel, l'authentification et le téléchargement de fichiers — le tout avec une sécurité de types de bout en bout.