3D Web with Three.js and React
PROGRAMMING LANGUAGES Feb. 11, 2026, 5:30 p.m.

3D Web with Three.js and React

Welcome to the world of immersive web experiences! By combining the power of Three.js—a lightweight 3D library—with the component-driven architecture of React, you can craft interactive visualizations that run smoothly in any modern browser. In this guide we’ll walk through setting up a React project, wiring Three.js into the component tree, and building a couple of practical demos you can extend for games, data visualizations, or product showcases.

Why Blend Three.js with React?

React excels at managing UI state, re‑rendering only what changes, while Three.js handles the heavy lifting of WebGL rendering. Marrying the two lets you keep your 3D scene declarative—think of each mesh as a React component that reacts to props and context. This separation of concerns reduces bugs, improves maintainability, and opens the door to React’s ecosystem (hooks, context, suspense) for 3D projects.

Another win is hot‑module reloading. When you tweak a geometry or material, React’s development server can instantly reflect the change without a full page refresh, speeding up iteration dramatically.

Setting Up the Project

First, bootstrap a fresh React app using Vite—its lightning‑fast dev server is perfect for graphics‑heavy workloads.

npm create vite@latest three-react-demo -- --template react
cd three-react-demo
npm install

Next, install Three.js and the React‑Three‑Fiber (R3F) bindings. R3F provides a <Canvas> component that abstracts the WebGL context and lets you write Three.js code as JSX.

npm install three @react-three/fiber

Optionally, add @react-three/drei for handy helpers like orbit controls, loaders, and environment maps.

npm install @react-three/drei

Your First 3D Scene

Create a new component called BoxScene.jsx. It will render a rotating cube, a simple yet powerful sanity check that everything is wired correctly.

import React, { useRef } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { MeshStandardMaterial, BoxGeometry } from 'three';

function RotatingBox() {
  const meshRef = useRef();

  // useFrame runs on every render loop
  useFrame((state, delta) => {
    meshRef.current.rotation.x += delta;
    meshRef.current.rotation.y += delta * 0.5;
  });

  return (
    <mesh ref={meshRef}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="#8AC" />
    </mesh>
  );
}

export default function BoxScene() {
  return (
    <Canvas camera={{ position: [3, 3, 5] }}>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} />
      <RotatingBox />
    </Canvas>
  );
}

Import BoxScene into App.jsx and render it. You should see a smoothly rotating cube, illuminated by ambient and directional lights.

Adding Interaction with React State

React’s state management shines when you want user‑driven changes. Let’s add a color picker that updates the cube’s material in real time.

import React, { useState } from 'react';
import BoxScene from './BoxScene';

export default function App() {
  const [color, setColor] = useState('#8AC');

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
      <div style={{ padding: '1rem' }}>
        <label>Cube Color:</label>
        <input
          type="color"
          value={color}
          onChange={(e) => setColor(e.target.value)}
          style={{ marginLeft: '0.5rem' }}
        />
      </div>
      <BoxScene color={color} />
    </div>
  );
}

Update BoxScene to accept the color prop and pass it to meshStandardMaterial. Because the material receives a new prop, React triggers a re‑render, and the cube instantly reflects the selected hue.

Prop‑driven Materials

function RotatingBox({ color }) {
  const meshRef = useRef();

  useFrame((state, delta) => {
    meshRef.current.rotation.x += delta;
    meshRef.current.rotation.y += delta * 0.5;
  });

  return (
    <mesh ref={meshRef}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color={color} />
    </mesh>
  );
}

export default function BoxScene({ color }) {
  return (
    <Canvas camera={{ position: [3, 3, 5] }}>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} />
      <RotatingBox color={color} />
    </Canvas>
  );
}

Loading External Models

Static geometries are fine for demos, but production apps often need GLTF or GLB assets. R3F’s useGLTF hook (from @react-three/drei) makes this painless.

import React, { Suspense } from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls, useGLTF } from '@react-three/drei';

function Model({ url }) {
  const { scene } = useGLTF(url);
  return <primitive object={scene} scale={0.5} />;
}

// Preload to avoid flash of empty content
useGLTF.preload('/models/car.glb');

export default function CarViewer() {
  return (
    <Canvas camera={{ position: [0, 2, 5] }}>
      <ambientLight intensity={0.7} />
      <directionalLight position={[5, 5, 5]} />
      <Suspense fallback={null}>
        <Model url="/models/car.glb" />
      </Suspense>
      <OrbitControls enablePan={false} />
    </Canvas>
  );
}

Wrap the model in <Suspense> so React can wait for the async loader without blocking the rest of the UI. OrbitControls adds mouse‑driven rotation, zoom, and pan, giving users a natural way to explore the 3D object.

Real‑World Use Cases

E‑commerce product configurators benefit from a React‑driven UI (color pickers, size selectors) that instantly updates a 3D model. Users can rotate, zoom, and view the product from any angle before purchasing.

Data visualizations such as 3D scatter plots or network graphs become more engaging when you replace SVG with WebGL. React’s state can drive the position, size, and color of each point, while Three.js handles the rendering performance.

Interactive learning tools (e.g., anatomy explorers or physics simulations) gain a declarative structure: each organ or particle is a component that reacts to user input, making the codebase easier to reason about.

Performance Tips

  • Keep the render loop lean. Only update objects that truly change each frame. Use useFrame sparingly.
  • Leverage InstancedMesh. For thousands of identical objects (e.g., particles), an InstancedMesh reduces draw calls dramatically.
  • Compress textures. Use formats like Basis or WebP and set texture.encoding = THREE.sRGBEncoding to avoid unnecessary GPU work.
  • Use memoization. Wrap static geometries and materials with React.memo or useMemo so they aren’t recreated on every render.
Pro Tip: When using InstancedMesh, store per‑instance data (position, scale, color) in a Float32Array and update it via instanceMatrix.needsUpdate = true. This pattern can render tens of thousands of objects at 60 fps on mid‑range hardware.

Advanced: Post‑Processing Effects

Three.js ships with an EffectComposer that chains render passes for bloom, depth‑of‑field, or color grading. R3F provides @react-three/postprocessing to integrate these passes declaratively.

import { Canvas } from '@react-three/fiber';
import { EffectComposer, Bloom, Vignette } from '@react-three/postprocessing';
import { Suspense } from 'react';
import { Model } from './Model';

export default function FancyScene() {
  return (
    <Canvas camera={{ position: [0, 1, 3] }}>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} />
      <Suspense fallback={null}>
        <Model url="/models/spacecraft.glb" />
      </Suspense>
      <EffectComposer multisampling={8}>
        <Bloom luminanceThreshold={0.2} luminanceSmoothing={0.9} height={300} />
        <Vignette eskil={false} offset={0.1} darkness={1.2} />
      </EffectComposer>
    </Canvas>
  );
}

The EffectComposer runs after the main scene render, applying the bloom and vignette passes. Adjust the parameters to match your visual style, and you’ll instantly elevate the perceived polish of the app.

Testing and Debugging

Because the 3D canvas lives inside the React tree, you can use standard testing tools like Jest and React Testing Library to verify prop flow. For visual debugging, enable the axesHelper and gridHelper from Three.js to see orientation and scale.

import { AxesHelper, GridHelper } from 'three';

function DebugHelpers() {
  return (
    <>
      <primitive object={new AxesHelper(5)} />
      <primitive object={new GridHelper(10, 10)} />
    
  );
}

Drop <DebugHelpers /> into any scene during development, then remove it for production builds.

Deploying to Production

When you’re ready to ship, run npm run build. Vite outputs an optimized bundle with code‑splitting and minification. Serve the dist folder via any static host (Netlify, Vercel, GitHub Pages). Remember to configure your server to serve index.html for unknown routes—otherwise deep links into a React‑router‑based 3D app will 404.

For larger assets (GLTF files, HDR environment maps), use a CDN with proper cache headers. This reduces latency and ensures smooth first‑paint times, especially on mobile networks.

Common Pitfalls & How to Avoid Them

  • Forgetting to dispose of geometries. Three.js does not automatically free GPU memory. Call geometry.dispose() and material.dispose() in a useEffect cleanup when components unmount.
  • Overusing React state for per‑frame data. Updating React state inside useFrame triggers a full React render, killing performance. Keep per‑frame values inside refs.
  • Mixing CSS transforms with WebGL. CSS animations on the canvas container can conflict with the internal render loop. Use Three.js for all visual motion inside the canvas.
Pro Tip: Use the useThree hook to access the renderer, scene, and camera directly. This is handy for custom post‑processing or when you need to read pixel data for analytics.

Next Steps

Now that you have a solid foundation, consider exploring these extensions:

  1. Integrate physics engines like use-cannon for realistic collisions.
  2. Combine with react-spring to animate properties (scale, opacity) with spring physics.
  3. Leverage server‑side rendering (SSR) with react-three-fiber on Next.js for SEO‑friendly 3D previews.

Each of these pathways deepens the interactivity and realism you can achieve, while still keeping the codebase clean and declarative.

Conclusion

Three.js and React together form a powerful duo for building modern, interactive 3D web experiences. By treating meshes, lights, and cameras as React components, you gain the benefits of a predictable UI paradigm, hot reloading, and a thriving ecosystem of hooks and helpers. From simple rotating cubes to sophisticated product configurators and data visualizations, the patterns covered here give you a launchpad for virtually any 3D project. Keep experimenting, profile performance early, and let the declarative nature of React guide your creative ambitions. Happy coding!

Share this article