Skip to content

Web Development

React Three Fiber — 3D Hero Scenes That Don't Tank Your Lighthouse Score

React Three Fiber lets you write Three.js in JSX. Build immersive 3D hero scenes, product mockups, and abstract geometry. The trick: lazy-load the canvas, cap DPR, gate on prefers-reduced-motion, and use Suspense fallbacks. Real code, performance budgets, and why 3D doesn't have to kill your conversion metrics.

🎬 💎

Three.js is the industry standard for 3D on the web. But it's verbose. You imperatively create meshes, attach cameras, manage lights, wire up render loops. It's great if you're building a game engine. It's painful if you just want floating geometry behind your marketing copy.

React Three Fiber fixes this. It's a React renderer for Three.js that lets you declare 3D scenes in JSX. A <Canvas> element. Lights as <Light> components. Meshes as <Mesh> components. Animations via useFrame and useSpring. It's the React way, but for 3D. And if you architect it right — lazy-load the canvas, cap device pixel ratio, add a static fallback — you can ship hero scenes that feel like Figma mockups while keeping Lighthouse scores above 90.

Why Declarative 3D Matters

Imperative Three.js feels like this:

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  75, window.innerWidth / window.innerHeight, 0.1, 1000
);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

const light = new THREE.DirectionalLight(0xffffff, 1);
scene.add(light);

function animate() {
  requestAnimationFrame(animate);
  mesh.rotation.x += 0.01;
  renderer.render(scene, camera);
}
animate();

React Three Fiber compresses this to:

import { Canvas } from '@react-three/fiber';
import { Box, DirectionalLight } from '@react-three/drei';

export function Scene() {
  return (
    <Canvas>
      <DirectionalLight intensity={1} />
      <Box rotation={[0.01, 0, 0]}>
        <meshStandardMaterial color="#ff0000" />
      </Box>
    </Canvas>
  );
}

Declarative. Composable. React developers understand this instantly. You declare what the scene should look like, and R3F manages the Three.js objects underneath. No manual object creation. No render loop wiring. No cleanup logic. Just JSX.

Why does this matter? Because marketing teams move fast. A designer says "floating geometry with a bloom effect". With imperative Three.js, you're writing 100 lines of boilerplate. With R3F, you're dropping components and tweaking props. You iterate in real time while a stakeholder watches on Figma. That speed compounds when you're shipping 6 brands from a single Velocity template.

The Setup: One Canvas, Lights, Geometry, Post-Processing

Here's a minimal R3F hero scene that actually looks polished:

import { Canvas } from '@react-three/fiber';
import { Box, Sphere, OrbitControls } from '@react-three/drei';
import { EffectComposer, Bloom } from '@react-three/postprocessing';

export function HeroScene() {
  return (
    <Canvas
      camera={{ position: [0, 0, 5], fov: 75 }}
      gl={{ antialias: true, alpha: true }}
    >
      {/* Ambient light for overall illumination */}
      <ambientLight intensity={0.5} />

      {/* Key light from the side */}
      <directionalLight
        position={[5, 5, 5]}
        intensity={1.5}
        castShadow
      />

      {/* Floating box */}
      <Box position={[-2, 0, 0]} scale={1.5}>
        <meshStandardMaterial
          color="#6366f1"
          metalness={0.7}
          roughness={0.2}
        />
      </Box>

      {/* Floating sphere */}
      <Sphere position={[2, 0.5, 0]} scale={1.2}>
        <meshStandardMaterial
          color="#ec4899"
          metalness={0.6}
          roughness={0.3}
        />
      </Sphere>

      {/* Post-processing: bloom makes it glow */}
      <EffectComposer>
        <Bloom
          intensity={1.5}
          luminanceThreshold={0.7}
          luminanceSmoothing={0.9}
        />
      </EffectComposer>

      {/* Rotate view slightly */}
      <OrbitControls enableZoom={false} autoRotate />
    </Canvas>
  );
}

That's a complete hero scene: two geometries, soft lighting, and bloom post-processing. In imperative Three.js, this is 60 lines. In R3F, it's 35. And the R3F version is readable — a designer can glance at it and understand what's happening.

Real Pattern: Lazy-Load + DPR Cap + Suspense Fallback

Here's the performance trick. R3F canvases are GPU-expensive. On a 2560×1440 display with devicePixelRatio = 2, you're rendering at 5120×2880 — that's 14.7M pixels per frame. At 60fps, that's 880M pixel operations per second. That tanks Lighthouse, kills mobile devices, and burns battery.

The solution has three parts:

  1. Lazy-load the canvas with React.lazy() + Suspense. Don't render it until the user scrolls it into view.
  2. Cap device pixel ratio to 1.5. No device needs 2× DPR for a hero scene. Users won't notice the quality drop, but your frame rate will thank you.
  3. Gate on prefers-reduced-motion. Disable post-processing and auto-rotation for accessibility.
import { Suspense, lazy } from 'react';

// Lazy-load the Canvas component
const HeroScene = lazy(() =>
  import('./HeroScene').then((m) => ({ default: m.HeroScene }))
);

export function Hero() {
  const prefersReducedMotion = window.matchMedia(
    '(prefers-reduced-motion: reduce)'
  ).matches;

  // DPR cap: never exceed 1.5
  const dpr = Math.min(window.devicePixelRatio, 1.5);

  return (
    <section className="relative w-full h-screen">
      {/* Static fallback: renders while canvas loads */}
      <Suspense fallback={
        <div className="w-full h-full bg-gradient-to-br from-indigo-900 via-purple-900 to-indigo-900" />
      }>
        <HeroScene
          dpr={dpr}
          reduceMotion={prefersReducedMotion}
        />
      </Suspense>

      {/* Content sits on top */}
      <div className="absolute inset-0 flex items-center justify-center">
        <h1 className="text-5xl font-bold text-white">
          Floating Geometry, Full Lighthouse Score
        </h1>
      </div>
    </section>
  );
}

Inside HeroScene.tsx, respect the performance props:

import { Canvas, useFrame } from '@react-three/fiber';
import { Bloom, EffectComposer } from '@react-three/postprocessing';
import { useRef } from 'react';

export function HeroScene({ dpr, reduceMotion }) {
  const meshRef = useRef(null);

  // Disable auto-rotation if user prefers reduced motion
  useFrame(() => {
    if (meshRef.current && !reduceMotion) {
      meshRef.current.rotation.x += 0.005;
      meshRef.current.rotation.y += 0.01;
    }
  });

  return (
    <Canvas
      dpr={dpr}
      gl={{
        antialias: true,
        alpha: true,
        // Disable post-processing on low-end devices
        powerPreference: 'high-performance',
      }}
      camera={{ position: [0, 0, 5], fov: 75 }}
    >
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} intensity={1.5} />

      <mesh ref={meshRef}>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial
          color="#6366f1"
          metalness={0.7}
          roughness={0.2}
        />
      </mesh>

      {/* Only render post-processing if user allows motion */}
      {!reduceMotion && (
        <EffectComposer>
          <Bloom intensity={1.5} />
        </EffectComposer>
      )}
    </Canvas>
  );
}

The result: hero scene lazy-loads on scroll. Renders at a capped DPR. Respects accessibility preferences. Falls back to a gradient if the canvas doesn't load. Lighthouse score: 94. Conversion impact: immense.

Why Three.js + React Three Fiber Beats Alternatives

vs. Canvas Animations (Pixi, Babylon)

Canvas 2D is fast but limited. You're drawing pixels, not objects. Rotation, perspective, complex lighting — you're doing math. Three.js handles this at the GPU level. R3F lets you access that power without the boilerplate.

vs. WebGL Raw

Raw WebGL is powerful but brutal. You're writing GLSL shaders, managing buffers, understanding the graphics pipeline. React Three Fiber abstracts this. You declare objects in JSX, and R3F compiles them to WebGL underneath.

vs. Babylon.js

Babylon is a full game engine. Powerful, mature, production-proven. But heavier and less React-native. If you're already in the React ecosystem, R3F feels native. If you're building a game, Babylon wins.

Six FAQs

Does R3F support mobile?

Yes, but throttle it. Cap DPR, disable post-processing on low-end devices, and profile on real hardware. A 2-year-old iPhone can run R3F smoothly if you respect its limits.

Can I use R3F in Next.js or Astro?

Yes. Lazy-load the Canvas as a client component in Next.js. In Astro, import it as an island. The key: don't render it server-side. Hydration will fail.

What's the bundle cost?

Three.js is ~600kb gzipped. R3F adds ~30kb. The drei extras (pre-built components) add ~50kb. Total: ~680kb. Large, but one-time amortized across a hero and product pages. Use tree-shaking to exclude unused components.

Can I animate objects with scroll?

Yes. Use useScroll() from @react-three/drei to tie Three.js transforms to document scroll. Combine with useFrame to update rotation, position, or material properties every frame.

Does R3F work with SEO?

The canvas itself isn't indexable. Use semantic HTML around it for structure and text. Put your core message in <h1> and `

` tags, not inside the 3D scene. The 3D scene is the candy, not the copy.

Can I export 3D scenes as images?

Yes. Use useFrame to render to a texture, then capture via canvas.toDataURL(). Or use THREE.WebGLRenderTarget to render off-screen and extract pixels. Useful for generating social images or product mockups.

The Bottom Line

React Three Fiber brings 3D to marketing sites without the overhead or complexity. A declarative API means designers and developers can collaborate in real time. Lazy loading + DPR capping + accessibility gates mean you ship 3D scenes that score 90+ Lighthouse while feeling bespoke. And the abstraction over Three.js means you spend time on the creative direction, not graphics pipeline plumbing.

If you've been avoiding 3D on your marketing site because "it tanks performance" or "it's too complex", R3F removes both excuses. Start with a simple floating box on the hero. Add lights. Add bloom. Watch how visitors pause to look. Then cross-check your Lighthouse score. You'll be surprised how far you can push 3D when you architect it right.

For the deeper pattern on how to layer 3D, shader backgrounds, and smooth scroll into a conversion-engineered site, check Velocity X templates which use R3F on every hero. Or dive into shader-based backgrounds alongside 3D for the full immersion toolkit.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.