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

AI Bot
Par AI Bot ·

Chargement du lecteur de synthèse vocale...

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-showcase

Maintenant, 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/three

Voici ce que fait chaque package :

PackageRôle
threeLa bibliothèque Three.js de base
@react-three/fiberRenderer React pour Three.js
@react-three/dreiAssistants pré-construits (contrôles, loaders, matériaux)
@react-three/rapierIntégration du moteur physique
@types/threeDé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 interne
  • camera définit la position initiale et le champ de vision
  • shadows active le shadow mapping globalement
  • mesh est 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 dev

Vous 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 jamais requestAnimationFrame directement — useFrame est 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 props distort et speed contrôlent la déformation.
  • Événements de pointeur — R3F supporte onClick, onPointerOver, onPointerOut et 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/postprocessing

Cré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 :

  • Suspense avec un Loader personnalisé — Affiche un spinner de chargement pendant le chargement des assets
  • Preload all — Précharge tous les assets du graphe de scène
  • dpr={[1, 2]} — Limite la plage de ratio de pixels pour des performances constantes
  • autoRotate — Fait tourner lentement la caméra quand l'utilisateur n'interagit pas
  • Html — 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 :

  1. Meshes instanciés — Si vous avez de nombreux objets identiques, utilisez InstancedMesh au 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>
  );
}
  1. 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().

  2. Compresser les textures — Utilisez le format KTX2 pour les textures au lieu de PNG/JPG. drei fournit useKTX2 pour cela.

  3. 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">
  1. Évitez les re-rendus — Utilisez useFrame pour les animations au lieu du state React. Les changements de state React déclenchent des re-rendus de composants ; useFrame s'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 shadows du 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 shaderMaterial de drei
  • Texte 3D — Utilisez Text3D de drei avec des polices personnalisées
  • Animations basées sur le scroll — Combinez R3F avec ScrollControls pour 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.


Vous voulez lire plus de tutoriels? Découvrez notre dernier tutoriel sur Libérer l'IA : Créer des récits animaliers captivants avec GPT et l'API TTS.

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.

30 min read·