React Three Fiber + Next.js : Créer des expériences web 3D interactives de A à Z

Introduction
Le web ne se limite plus aux interfaces plates. Des configurateurs de produits aux visualisations de données, en passant par les portfolios interactifs et les jeux dans le navigateur, le contenu 3D sur le web devient un outil standard pour chaque développeur. Three.js est depuis longtemps la bibliothèque de référence pour le rendu WebGL, mais son API impérative peut sembler étrangère aux développeurs React habitués aux composants déclaratifs.
React Three Fiber (R3F) comble cette lacune. C'est un renderer React pour Three.js qui vous permet de construire des scènes 3D en utilisant des composants JSX, des hooks et le même modèle mental que vous utilisez déjà pour votre interface utilisateur. Combiné avec @react-three/drei (une collection d'assistants utiles) et @react-three/rapier (un moteur physique), vous pouvez créer des expériences 3D de qualité production sans jamais écrire de WebGL brut.
Dans ce tutoriel, vous allez construire une vitrine de produit 3D interactive complète — une sphère flottante que les utilisateurs peuvent faire tourner, cliquer pour changer les couleurs, et observer rebondir avec de la physique réaliste. En cours de route, vous maîtriserez les concepts fondamentaux de R3F et apprendrez à l'intégrer proprement dans une application Next.js.
Ce que vous allez construire
À la fin de ce tutoriel, vous aurez :
- Une application Next.js avec un canvas 3D plein écran
- Une scène 3D avec éclairage, ombres et cartes d'environnement
- Des contrôles d'orbite interactifs pour le mouvement de caméra
- Des objets 3D animés avec de la physique basée sur les ressorts
- Un changement de couleur des matériaux au clic
- Un plan de sol avec physique et objets rebondissants
- Des optimisations de performance pour le déploiement en production
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 20+ installé sur votre machine
- Des connaissances de base en React et TypeScript
- Une familiarité avec Next.js App Router (les pages fonctionnent aussi, mais nous utilisons App Router)
- Un éditeur de code comme VS Code avec l'extension ES7+ React
- Aucune expérience préalable en Three.js ou 3D requise — on part de zéro
Étape 1 : Configuration du projet
Créez un nouveau projet Next.js et installez les dépendances requises :
npx create-next-app@latest r3f-showcase --typescript --tailwind --app --src-dir
cd r3f-showcaseMaintenant, installez React Three Fiber et ses packages écosystème :
npm install three @react-three/fiber @react-three/drei @react-three/rapier
npm install -D @types/threeVoici ce que fait chaque package :
| Package | Rôle |
|---|---|
three | La bibliothèque Three.js de base |
@react-three/fiber | Renderer React pour Three.js |
@react-three/drei | Assistants pré-construits (contrôles, loaders, matériaux) |
@react-three/rapier | Intégration du moteur physique |
@types/three | Définitions de types TypeScript |
Étape 2 : Comprendre le Canvas
La fondation de toute application R3F est le composant Canvas. Il crée un contexte WebGL et gère la boucle de rendu Three.js pour vous.
Créez un nouveau fichier src/components/Scene.tsx :
"use client";
import { Canvas } from "@react-three/fiber";
export default function Scene() {
return (
<div className="h-screen w-full">
<Canvas
camera={{ position: [0, 2, 5], fov: 50 }}
shadows
>
{/* Notre scène 3D ira ici */}
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="royalblue" />
</mesh>
</Canvas>
</div>
);
}Quelques points importants à noter :
"use client"est obligatoire car R3F utilise des API navigateur (WebGL) et des hooks React en internecameradéfinit la position initiale et le champ de visionshadowsactive le shadow mapping globalementmeshest le bloc de construction de base — une géométrie plus un matériau
Mettez à jour src/app/page.tsx pour afficher la scène :
import dynamic from "next/dynamic";
const Scene = dynamic(() => import("@/components/Scene"), {
ssr: false,
loading: () => (
<div className="flex h-screen w-full items-center justify-center bg-black">
<p className="text-white">Chargement de la scène 3D...</p>
</div>
),
});
export default function Home() {
return <Scene />;
}Utilisez toujours l'import dynamic avec ssr: false pour les composants R3F dans Next.js. Three.js accède à window et document, qui n'existent pas pendant le rendu côté serveur.
Lancez le serveur de développement pour voir votre premier cube 3D :
npm run devVous devriez voir un cube bleu sur fond noir. Il paraît plat car nous n'avons pas encore de lumières.
Étape 3 : Ajouter des lumières et un environnement
L'éclairage est ce qui transforme des objets plats en visuels 3D réalistes. Créez src/components/Lighting.tsx :
"use client";
import { Environment } from "@react-three/drei";
export default function Lighting() {
return (
<>
{/* Lumière ambiante pour l'illumination de base */}
<ambientLight intensity={0.3} />
{/* Lumière directionnelle principale (comme le soleil) */}
<directionalLight
position={[5, 8, 5]}
intensity={1.5}
castShadow
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
shadow-camera-far={20}
shadow-camera-left={-5}
shadow-camera-right={5}
shadow-camera-top={5}
shadow-camera-bottom={-5}
/>
{/* Lumière de remplissage depuis le côté opposé */}
<directionalLight
position={[-3, 4, -5]}
intensity={0.4}
/>
{/* Carte d'environnement HDR pour des réflexions réalistes */}
<Environment preset="city" />
</>
);
}Le composant Environment de drei charge une carte d'environnement HDR qui fournit des réflexions réalistes sur les surfaces métalliques et brillantes. La prop preset offre plusieurs options intégrées : city, sunset, dawn, night, warehouse, forest, apartment, studio, park et lobby.
Étape 4 : Créer l'objet 3D interactif
Maintenant, construisons la vedette de notre vitrine — une sphère 3D interactive qui réagit aux événements de survol et de clic. Créez src/components/InteractiveSphere.tsx :
"use client";
import { useRef, useState } from "react";
import { useFrame } from "@react-three/fiber";
import { MeshDistortMaterial } from "@react-three/drei";
import type { Mesh } from "three";
const COLORS = ["#ff6b6b", "#4ecdc4", "#45b7d1", "#96ceb4", "#feca57"];
export default function InteractiveSphere() {
const meshRef = useRef<Mesh>(null);
const [colorIndex, setColorIndex] = useState(0);
const [hovered, setHovered] = useState(false);
// Boucle d'animation — s'exécute à chaque frame (~60fps)
useFrame((state, delta) => {
if (!meshRef.current) return;
// Animation de flottement douce
meshRef.current.position.y =
Math.sin(state.clock.elapsedTime * 0.8) * 0.3 + 1.5;
// Rotation lente
meshRef.current.rotation.y += delta * 0.3;
// Agrandissement au survol
const targetScale = hovered ? 1.2 : 1;
meshRef.current.scale.lerp(
{ x: targetScale, y: targetScale, z: targetScale } as any,
0.1
);
});
const handleClick = () => {
setColorIndex((prev) => (prev + 1) % COLORS.length);
};
return (
<mesh
ref={meshRef}
castShadow
onClick={handleClick}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
>
<sphereGeometry args={[1, 64, 64]} />
<MeshDistortMaterial
color={COLORS[colorIndex]}
roughness={0.2}
metalness={0.8}
distort={hovered ? 0.4 : 0.2}
speed={2}
/>
</mesh>
);
}Concepts clés introduits ici :
useFrame— Le hook d'animation de R3F. Il s'exécute à chaque frame de rendu, vous donnant accès à l'horloge Three.js et au temps delta. N'utilisez jamaisrequestAnimationFramedirectement —useFrameest intégré à la boucle de rendu de R3F.MeshDistortMaterial— Un assistant drei qui crée un effet de surface ondulée et déformée. Les propsdistortetspeedcontrôlent la déformation.- Événements de pointeur — R3F supporte
onClick,onPointerOver,onPointerOutet plus encore, exactement comme les éléments DOM React classiques. En coulisses, il utilise le raycasting pour déterminer avec quel objet 3D la souris interagit. lerp— Interpolation linéaire pour des transitions fluides. Au lieu de sauter directement à l'échelle cible, on anime progressivement vers elle à chaque frame.
Étape 5 : Ajouter les contrôles d'orbite
Permettez aux utilisateurs de faire pivoter et zoomer la caméra autour de la scène. Mettez à jour votre composant Scene :
"use client";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, ContactShadows } from "@react-three/drei";
import Lighting from "./Lighting";
import InteractiveSphere from "./InteractiveSphere";
export default function Scene() {
return (
<div className="h-screen w-full bg-gradient-to-b from-gray-900 to-black">
<Canvas
camera={{ position: [0, 2, 5], fov: 50 }}
shadows
>
<Lighting />
<InteractiveSphere />
{/* Plan de sol avec ombres de contact */}
<ContactShadows
position={[0, -0.5, 0]}
opacity={0.5}
scale={10}
blur={2}
far={4}
/>
{/* Contrôles de caméra */}
<OrbitControls
enablePan={false}
minDistance={3}
maxDistance={10}
minPolarAngle={Math.PI / 6}
maxPolarAngle={Math.PI / 2.2}
/>
</Canvas>
</div>
);
}La configuration d'OrbitControls limite le mouvement de la caméra pour empêcher les utilisateurs de descendre sous le sol ou de zoomer trop loin. ContactShadows crée une ombre douce sous l'objet sans avoir besoin d'un plan de sol physique.
Étape 6 : Charger des modèles 3D
Les projets réels utilisent souvent des modèles 3D au lieu de formes primitives. R3F supporte nativement les modèles GLTF/GLB grâce au hook useGLTF de drei.
Créez un composant de chargement de modèle dans src/components/Model.tsx :
"use client";
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { useGLTF, Float } from "@react-three/drei";
import type { Group } from "three";
interface ModelProps {
url: string;
scale?: number;
position?: [number, number, number];
}
export default function Model({ url, scale = 1, position = [0, 0, 0] }: ModelProps) {
const groupRef = useRef<Group>(null);
const { scene } = useGLTF(url);
useFrame((state) => {
if (!groupRef.current) return;
groupRef.current.rotation.y = state.clock.elapsedTime * 0.2;
});
return (
<Float
speed={1.5}
rotationIntensity={0.3}
floatIntensity={0.5}
>
<group ref={groupRef} position={position} scale={scale}>
<primitive object={scene.clone()} castShadow receiveShadow />
</group>
</Float>
);
}Le composant Float de drei ajoute automatiquement une animation de flottement douce. Le hook useGLTF gère le chargement, la mise en cache et la libération des modèles GLTF.
Vous pouvez trouver des modèles 3D gratuits sur poly.pizza, sketchfab.com ou market.pmnd.rs. Téléchargez-les au format GLB et placez-les dans votre répertoire public/models/.
Pour précharger les modèles et éviter les flashs de chargement :
// En bas de votre fichier de composant Model
useGLTF.preload("/models/sneaker.glb");Étape 7 : Ajouter la physique
La physique donne vie à votre scène avec des interactions réalistes. Créez src/components/PhysicsScene.tsx :
"use client";
import { Physics, RigidBody, CuboidCollider } from "@react-three/rapier";
import { useState } from "react";
function BouncingBall({ position }: { position: [number, number, number] }) {
const [color, setColor] = useState("#ff6b6b");
return (
<RigidBody
position={position}
restitution={0.8}
friction={0.5}
colliders="ball"
>
<mesh
castShadow
onClick={() => setColor(`hsl(${Math.random() * 360}, 70%, 60%)`)}
>
<sphereGeometry args={[0.3, 32, 32]} />
<meshStandardMaterial color={color} roughness={0.3} metalness={0.6} />
</mesh>
</RigidBody>
);
}
function Ground() {
return (
<RigidBody type="fixed">
<CuboidCollider args={[10, 0.1, 10]} position={[0, -1, 0]} />
<mesh receiveShadow position={[0, -1, 0]}>
<boxGeometry args={[20, 0.2, 20]} />
<meshStandardMaterial color="#1a1a2e" />
</mesh>
</RigidBody>
);
}
export default function PhysicsScene() {
const balls = Array.from({ length: 10 }, (_, i) => ({
id: i,
position: [
(Math.random() - 0.5) * 4,
3 + Math.random() * 5,
(Math.random() - 0.5) * 4,
] as [number, number, number],
}));
return (
<Physics gravity={[0, -9.81, 0]}>
<Ground />
{balls.map((ball) => (
<BouncingBall key={ball.id} position={ball.position} />
))}
</Physics>
);
}Le composant Physics enveloppe votre scène et applique la gravité. RigidBody fait participer les objets à la simulation physique. Propriétés clés :
restitution— Rebond (0 = pas de rebond, 1 = rebond parfait)friction— Friction de surface (0 = glace, 1 = caoutchouc)type="fixed"— Rend l'objet immobile (parfait pour les sols et murs)colliders— Génère automatiquement les formes de collision (ball,cuboid,hull,trimesh)
Étape 8 : Effets de post-traitement
Ajoutez une qualité cinématographique avec le post-traitement. Installez le package d'effets :
npm install @react-three/postprocessingCréez src/components/Effects.tsx :
"use client";
import { EffectComposer, Bloom, ChromaticAberration, Vignette } from "@react-three/postprocessing";
export default function Effects() {
return (
<EffectComposer>
<Bloom
luminanceThreshold={0.6}
luminanceSmoothing={0.9}
intensity={0.5}
/>
<Vignette eskil={false} offset={0.1} darkness={0.5} />
<ChromaticAberration offset={[0.0005, 0.0005]} />
</EffectComposer>
);
}- Bloom — Crée un effet de lueur sur les surfaces brillantes
- Vignette — Assombrit les bords de l'écran pour un rendu cinématographique
- ChromaticAberration — Ajoute un effet subtil de frange de couleur
Les effets de post-traitement peuvent impacter significativement les performances sur les appareils mobiles. Fournissez toujours un moyen de les désactiver, ou utilisez le hook useDetectGPU de drei pour ajuster automatiquement la qualité.
Étape 9 : Qualité responsive et adaptative
Les applications 3D en production doivent fonctionner sur tout, des ordinateurs haut de gamme aux téléphones entrée de gamme. Créez src/components/AdaptiveScene.tsx :
"use client";
import { useThree } from "@react-three/fiber";
import { useDetectGPU, PerformanceMonitor } from "@react-three/drei";
import { useState, useEffect } from "react";
export function AdaptivePixelRatio() {
const { gl } = useThree();
const GPUTier = useDetectGPU();
useEffect(() => {
if (GPUTier.tier === 0 || GPUTier.isMobile) {
gl.setPixelRatio(1);
} else if (GPUTier.tier === 1) {
gl.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
} else {
gl.setPixelRatio(Math.min(window.devicePixelRatio, 2));
}
}, [GPUTier, gl]);
return null;
}
export function AdaptivePerformance({
children,
}: {
children: (dpr: number) => React.ReactNode;
}) {
const [dpr, setDpr] = useState(1.5);
return (
<PerformanceMonitor
onIncline={() => setDpr(Math.min(dpr + 0.5, 2))}
onDecline={() => setDpr(Math.max(dpr - 0.5, 0.5))}
>
{children(dpr)}
</PerformanceMonitor>
);
}Le PerformanceMonitor surveille les taux de frames et ajuste automatiquement la qualité. Quand les frames chutent, il réduit le ratio de pixels ; quand les performances sont bonnes, il augmente la qualité.
Étape 10 : Assembler le tout
Maintenant, combinez tout dans la scène finale. Mettez à jour src/components/Scene.tsx :
"use client";
import { Suspense } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, ContactShadows, Html, Preload } from "@react-three/drei";
import Lighting from "./Lighting";
import InteractiveSphere from "./InteractiveSphere";
import Effects from "./Effects";
import { AdaptivePixelRatio } from "./AdaptiveScene";
function Loader() {
return (
<Html center>
<div className="flex flex-col items-center gap-2">
<div className="h-8 w-8 animate-spin rounded-full border-2 border-white border-t-transparent" />
<p className="text-sm text-white">Chargement de la scène...</p>
</div>
</Html>
);
}
export default function Scene() {
return (
<div className="relative h-screen w-full bg-gradient-to-b from-gray-900 to-black">
{/* Superposition UI */}
<div className="absolute left-0 top-0 z-10 p-6">
<h1 className="text-3xl font-bold text-white">Vitrine Produit 3D</h1>
<p className="mt-2 text-gray-400">Cliquez sur la sphère pour changer les couleurs</p>
<p className="text-gray-400">Glissez pour orbiter • Défilez pour zoomer</p>
</div>
<Canvas
camera={{ position: [0, 2, 5], fov: 50 }}
shadows
dpr={[1, 2]}
gl={{ antialias: true, alpha: false }}
>
<AdaptivePixelRatio />
<Suspense fallback={<Loader />}>
<Lighting />
<InteractiveSphere />
<ContactShadows
position={[0, -0.5, 0]}
opacity={0.5}
scale={10}
blur={2}
far={4}
/>
<Effects />
<Preload all />
</Suspense>
<OrbitControls
enablePan={false}
minDistance={3}
maxDistance={10}
minPolarAngle={Math.PI / 6}
maxPolarAngle={Math.PI / 2.2}
autoRotate
autoRotateSpeed={0.5}
/>
</Canvas>
</div>
);
}Ajouts clés :
Suspenseavec unLoaderpersonnalisé — Affiche un spinner de chargement pendant le chargement des assetsPreload all— Précharge tous les assets du graphe de scènedpr={[1, 2]}— Limite la plage de ratio de pixels pour des performances constantesautoRotate— Fait tourner lentement la caméra quand l'utilisateur n'interagit pasHtml— Rend du HTML classique à l'intérieur de la scène 3D, toujours face à la caméra
Bonnes pratiques de performance
Lors du déploiement de R3F en production, gardez ces règles en tête :
- Meshes instanciés — Si vous avez de nombreux objets identiques, utilisez
InstancedMeshau lieu de meshes individuels. Cela réduit considérablement les draw calls.
import { Instances, Instance } from "@react-three/drei";
function Particles() {
return (
<Instances limit={1000}>
<sphereGeometry args={[0.05, 8, 8]} />
<meshStandardMaterial color="white" />
{positions.map((pos, i) => (
<Instance key={i} position={pos} />
))}
</Instances>
);
}-
Libérer les ressources — R3F gère cela automatiquement pour les composants qui se démontent, mais si vous créez des matériaux ou géométries manuellement, appelez toujours
.dispose(). -
Compresser les textures — Utilisez le format KTX2 pour les textures au lieu de PNG/JPG. drei fournit
useKTX2pour cela. -
Utilisez
frameloop="demand"— Si votre scène est principalement statique, définissez ceci sur Canvas pour ne rendre que quand quelque chose change :
<Canvas frameloop="demand">- Évitez les re-rendus — Utilisez
useFramepour les animations au lieu du state React. Les changements de state React déclenchent des re-rendus de composants ;useFrames'exécute en dehors de la réconciliation React.
Dépannage
Erreur "window is not defined"
Cela arrive quand le code R3F s'exécute pendant le rendu côté serveur. Utilisez toujours les imports dynamiques avec ssr: false :
const Scene = dynamic(() => import("@/components/Scene"), { ssr: false });Écran noir sans erreurs
Généralement un problème d'éclairage. Assurez-vous d'avoir au moins une source de lumière. Essayez d'ajouter <ambientLight intensity={1} /> pour vérifier que les objets existent.
Performances médiocres sur mobile
- Réduisez la complexité géométrique (moins de segments)
- Diminuez le ratio de pixels :
dpr={[0.5, 1]} - Désactivez les ombres : supprimez la prop
shadowsdu Canvas - Supprimez les effets de post-traitement
Les objets ne reçoivent pas les clics
R3F utilise le raycasting pour les événements de pointeur. Assurez-vous que le mesh a une géométrie attachée — les géométries invisibles ou de taille zéro ne captent pas les clics.
Prochaines étapes
Maintenant que vous maîtrisez les fondamentaux, explorez ces domaines :
- Matériaux Shader — Écrivez des shaders GLSL personnalisés avec
shaderMaterialde drei - Texte 3D — Utilisez
Text3Dde drei avec des polices personnalisées - Animations basées sur le scroll — Combinez R3F avec
ScrollControlspour des expériences 3D pilotées par le défilement - VR/AR — R3F supporte WebXR nativement avec le composant
XR - Multijoueur — Combinez avec WebSockets pour des expériences 3D partagées
Conclusion
React Three Fiber transforme le développement web 3D d'une compétence de niche en quelque chose que tout développeur React peut acquérir. En traitant les objets 3D comme des composants, en utilisant des hooks pour l'animation, et en tirant parti de l'extensive bibliothèque d'assistants drei, vous pouvez construire des expériences 3D de qualité production sans expertise approfondie en Three.js.
Les points clés de ce tutoriel :
- Canvas est votre point d'entrée — enveloppez toujours le contenu R3F dedans
- useFrame est votre boucle d'animation — n'utilisez jamais
requestAnimationFrame - drei est votre boîte à outils — vérifiez-la avant de construire des solutions personnalisées
- Les imports dynamiques avec SSR désactivé sont obligatoires dans Next.js
- Les performances évoluent avec la conscience — le ratio de pixels, l'instancing et le LOD comptent
Commencez simple, itérez, et laissez la nature déclarative de React guider l'architecture de votre scène 3D. Le web 3D ne fait que commencer, et vous êtes maintenant équipé pour le construire.
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

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.

Construire une application complète avec Firebase et Next.js 15 : Auth, Firestore et temps réel
Apprenez à créer une application full-stack avec Next.js 15 et Firebase. Ce guide couvre l'authentification, Firestore, les mises à jour en temps réel, les Server Actions et le déploiement sur Vercel.

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.