EmpireUI
Get Pro
← Blog8 min read#react-three-fiber#threejs#3d-graphics

React Three Fiber: 3D Graphics in React Without WebGL Expertise

React Three Fiber lets you build real 3D scenes with JSX and hooks. No raw WebGL required. Here's how to get started without losing your mind.

Abstract 3D geometric shapes rendered with glowing blue and purple lighting on a dark background

What Is React Three Fiber and Why Should You Care

Honestly, writing raw WebGL is one of the most painful experiences in frontend development. Shader programs, buffer objects, attribute pointers — it's a lot of boilerplate before you get a single triangle on screen. React Three Fiber (r3f) exists to fix that, and it does the job well.

React Three Fiber is a React renderer for Three.js. That sentence sounds simple but it means something genuinely useful: you write your 3D scene as JSX components, use hooks for animation and interaction, and React manages the scene graph the same way it manages your UI. No canvas imperative code scattered everywhere.

The library is maintained by Pmndrs (the same folks behind Zustand and Jotai) and sits at version 8.x as of late 2026. The ecosystem around it — Drei, Rapier physics, React Spring integrations — is mature enough that you can build production-quality 3D UIs without touching the Three.js API directly in most cases.

It pairs surprisingly well with effects like particles background animations and glassmorphism overlays. If you're already doing visual-heavy React work, r3f slots in naturally.

Setting Up React Three Fiber in a Next.js or Vite Project

Installation is two packages. Three.js itself, and the r3f wrapper. You'll also want @react-three/drei immediately — it's a collection of helpers that you'll use constantly.

npm install three @react-three/fiber @react-three/drei
# TypeScript types
npm install -D @types/three

In a Next.js 15 app, the Canvas component from r3f needs to run on the client. Wrap it in a client component or use dynamic with { ssr: false }. Vite projects don't have this constraint. The Canvas creates a WebGL context and sets up the Three.js renderer automatically — you don't configure any of that yourself.

Your First 3D Scene: Geometry, Materials, and Lights

The mental model shift you need: Three.js objects map to JSX elements. A BoxGeometry becomes <boxGeometry />. A MeshStandardMaterial becomes <meshStandardMaterial />. The camelCase-to-lowercase conversion is the main thing to internalize.

import { Canvas, useFrame } from '@react-three/fiber'
import { useRef } from 'react'
import * as THREE from 'three'

function RotatingBox() {
  const meshRef = useRef<THREE.Mesh>(null)

  useFrame((state, delta) => {
    if (!meshRef.current) return
    meshRef.current.rotation.x += delta * 0.5
    meshRef.current.rotation.y += delta * 0.8
  })

  return (
    <mesh ref={meshRef}>
      <boxGeometry args={[1.5, 1.5, 1.5]} />
      <meshStandardMaterial
        color="#6366f1"
        roughness={0.3}
        metalness={0.7}
      />
    </mesh>
  )
}

export default function Scene() {
  return (
    <Canvas
      camera={{ position: [0, 0, 5], fov: 60 }}
      style={{ height: '100vh', background: '#0f0f0f' }}
    >
      <ambientLight intensity={0.4} />
      <directionalLight position={[10, 10, 5]} intensity={1.2} />
      <RotatingBox />
    </Canvas>
  )
}

The useFrame hook is the r3f equivalent of requestAnimationFrame. It fires every frame and gives you delta (time since last frame in seconds) so your animations run at the same perceived speed regardless of frame rate. Always use delta for rotation and movement — don't assume 60fps.

Using Drei Helpers to Skip the Boilerplate

@react-three/drei is where you'll spend most of your time after the basics click. It exports hundreds of components for things you'd otherwise write from scratch: orbit controls, environment maps, text rendering, shadows, portals.

<OrbitControls /> is probably the first one you'll use. Drop it inside Canvas and your scene immediately becomes mouse-draggable and zoomable. <Environment preset="city" /> adds realistic lighting from an HDR in one line. <Text> renders 3D text without loading custom fonts manually.

For loading external 3D models (GLB, GLTF files), Drei's useGLTF hook handles the async loading and gives you a typed scene graph. Combine it with React Suspense and you get a loading state that fits naturally into your React component tree. It's the same pattern you'd use for data fetching — your muscle memory transfers.

If you're building UI components that sit on top of a 3D scene — buttons, tooltips, modals — check out <Html> from Drei. It projects DOM elements into 3D space with proper z-sorting. Works well alongside glassmorphism cards for that layered depth effect.

Performance: What to Watch and What to Ignore

3D on the web has one hard constraint: the main thread. If your JavaScript blocks for more than a frame (16ms at 60fps), you'll drop frames. The good news is r3f's render loop runs off React's reconciler in a way that doesn't re-render the entire scene every time your app state changes — only geometry and material updates trigger Three.js mutations.

The things that actually hurt performance: too many draw calls (each mesh is a draw call), large uncompressed textures, and running heavy calculations inside useFrame without memoization. Use <InstancedMesh> when you need to render hundreds of identical objects. Compress your textures with Basis or KTX2 — a 4MB PNG becomes 400KB and loads in a third of the time.

For general React performance principles that also apply here, this guide on React performance covers memoization and avoiding unnecessary re-renders in detail. The same rules apply inside r3f component trees.

What you can mostly ignore at the start: shader optimization. Unless you're writing custom shaders (GLSL), Three.js handles shader compilation. Don't pre-optimize material types until you actually see frame drops in the profiler.

Animating with React Spring and r3f

@react-spring/three integrates spring physics directly into Three.js objects. Instead of manually updating position in useFrame, you declare a target value and the library interpolates toward it with configurable tension and friction. It's the same API as react-spring for DOM elements.

import { useSpring, animated } from '@react-spring/three'

function SpringBox({ hovered }: { hovered: boolean }) {
  const { scale, color } = useSpring({
    scale: hovered ? 1.4 : 1.0,
    color: hovered ? '#f472b6' : '#6366f1',
    config: { tension: 200, friction: 20 },
  })

  return (
    <animated.mesh scale={scale}>
      <boxGeometry args={[1, 1, 1]} />
      <animated.meshStandardMaterial color={color} />
    </animated.mesh>
  )
}

The animated.mesh and animated.meshStandardMaterial are the Three.js equivalents of animated.div. The library handles the frame-by-frame interpolation outside React's render cycle, which keeps it performant. This pattern pairs well with hover and click interactions on 3D objects.

Integrating r3f with Your Existing React UI Stack

Here's the thing: most r3f tutorials treat the 3D canvas as the entire page. In real apps you're embedding a canvas inside a UI that has navigation, sidebars, modals, and all the usual React stuff. That integration is straightforward but has a few rough edges worth knowing.

The Canvas component takes up whatever space its container gives it. Set width: 100% and height: 100% on the container, or an explicit pixel height. Don't set height on Canvas directly via a className that Tailwind might compute to 0 — use inline styles or a dedicated wrapper with a known height.

State sharing between the 3D scene and the rest of your UI works fine with Zustand or React context. The r3f scene is just React components, so they can read from the same stores. You can update a Zustand store from a click on a 3D mesh and have a regular React modal open in response. If you're using a theme toggle in your React app, you can read the current theme in your r3f material colors and respond to dark/light mode switches.

One thing that trips people up: the Canvas creates its own React root internally for the Three.js scene. Context providers above the Canvas don't automatically flow into it. Use the eventSource and eventPrefix props, or re-provide your context using Drei's <Preload> utilities if you need store access inside the scene.

Real Use Cases: When to Actually Use React Three Fiber

Not every project needs 3D. That's worth saying plainly. But r3f earns its place in specific situations: product configurators, interactive data visualizations, hero sections with depth, portfolio showpieces, and game-adjacent experiences.

For SaaS landing pages, a subtle animated 3D object in the hero section can differentiate from the sea of Lottie animations and CSS gradients. Keep the polygon count low and use <AdaptiveDpr /> from Drei to automatically reduce resolution on mobile — nobody wants a dead battery from your marketing site. If you want to add toast notifications or status feedback alongside your 3D scene, React toast notifications integrate just like any other UI layer on top.

E-commerce product viewers are another strong use case. A 3D model that rotates on drag, with material variants swapped via UI controls, is genuinely better than a lightbox of static images for certain product types. The tooling in r3f and Drei makes this buildable in a week, not a month.

Where r3f doesn't make sense: dashboards with mostly charts and tables, content-heavy pages, anything where the 3D adds no informational or aesthetic value. Shipping JavaScript for a WebGL context that sits unused is just waste.

FAQ

Do I need to know Three.js before learning React Three Fiber?

Not strictly. You can get a working scene following r3f docs without deep Three.js knowledge. But when something breaks — a material renders wrong, a geometry has unexpected normals — you'll need to look up the Three.js docs to understand why. Learning the basics of Three.js geometry types, materials, and lights will save you debugging time.

Does React Three Fiber work with Next.js App Router?

Yes, but Canvas must render on the client. Add 'use client' at the top of any file that imports Canvas, or use Next.js dynamic imports with ssr: false. In Next.js 15+, the App Router defaults to server components, so this is a required step — the WebGL context doesn't exist on the server.

How do I handle 3D model loading with React Suspense in r3f?

Use useGLTF from @react-three/drei inside a component, then wrap that component in a React Suspense boundary with a fallback. Drei's useGLTF preloads assets and throws a promise while loading, which Suspense catches. Call useGLTF.preload('/model.glb') outside the component to start loading before the component mounts.

What's the performance difference between useFrame and useSpring for animations?

Both run outside React's render cycle, so neither causes unnecessary React re-renders. useFrame gives you manual control on every frame — good for procedural animations tied to time or physics you calculate yourself. useSpring is better for value interpolation between two states (hover, click, toggle) because the spring physics handle easing and overshoot naturally without you writing the math.

Can I mix regular DOM elements with Three.js objects in the same scene?

Yes, using Drei's Html component. It renders a DOM element that follows a point in 3D space. It handles z-sorting so the DOM element appears in front of or behind 3D objects correctly. Performance degrades with many Html instances because each one is a separate DOM subtree — keep it to labels, tooltips, and occasional UI overlays.

Is React Three Fiber production-ready for commercial projects?

Yes. r3f 8.x is stable and used in production by multiple companies. The Pmndrs ecosystem (Drei, Rapier, React Spring) is actively maintained. The main risk is bundle size — Three.js itself is roughly 600KB minified before tree-shaking. Use specific imports and check your bundle after setup.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

react-three-fiber Intro: 3D Scenes in React With Three.jsCanvas Animations in React: requestAnimationFrame, Particles, PathsWebGL Background Effects Without Three.js: Raw Shaders in ReactThree.js with React: Particles, Blobs and Interactive 3D Scenes