Interactive 3D can make a mobile product feel tangible: rotate a product, inspect an object, preview a space or turn progress into a small world. The first demo is usually easy. The production work begins when that demo has to load quickly, stay responsive and survive a three-year-old Android phone.
React Native can do this without putting the experience in a WebView. The common
Expo stack combines Three.js, React Three Fiber and expo-gl, but each layer has
a different responsibility.
Understand the stack before choosing it
Three.js owns the scene graph, cameras, geometry, materials, lighting, animation and asset loaders. React Three Fiber is a React renderer for Three.js, so a mesh becomes a component and scene composition follows familiar React patterns. Expo GL provides the native OpenGL ES render target used on iOS and Android. Expo Asset and the native React Three Fiber integration bridge files and textures that would normally be loaded through browser APIs.
For a stable React 19 project today, pair React Three Fiber 9 with its native
entry point and pin compatible versions of Three.js, Fiber, Drei and Expo. The
next major native architecture is moving toward a separate
@react-three/native package and WebGPU-capable backends, but that path is still
pre-alpha. It is exciting research, not a production default.
A minimal scene remains pleasantly small:
import { Canvas, useFrame } from '@react-three/fiber/native';
import { useRef } from 'react';
import type { Mesh } from 'three';
function ObjectPreview() {
const mesh = useRef<Mesh>(null);
useFrame((_, delta) => {
if (mesh.current) mesh.current.rotation.y += delta * 0.35;
});
return (
<mesh ref={mesh}>
<icosahedronGeometry args={[1, 2]} />
<meshStandardMaterial color="#8db5f8" roughness={0.65} />
</mesh>
);
}
export function ProductScene() {
return (
<Canvas camera={{ position: [0, 0, 3.4], fov: 42 }} dpr={[1, 1.5]}>
<ambientLight intensity={1.2} />
<directionalLight position={[3, 4, 5]} intensity={2.4} />
<ObjectPreview />
</Canvas>
);
}
Use @react-three/drei/native selectively for native-compatible helpers. The
native export is not identical to the web package: DOM-dependent helpers such as
Html are not available, and some advanced effects assume WebGL features that
Expo GL does not implement.
Treat 3D assets like application code
Use glTF or binary GLB as the handoff format. Keep the source model in the design pipeline and export a mobile-specific asset rather than shipping the artist’s original scene.
Before the model reaches the app:
- remove invisible geometry, unused bones, cameras and animation tracks;
- merge materials where it reduces draw calls;
- bake lighting when it does not need to change at runtime;
- resize textures to the largest size they will actually occupy on screen;
- use mipmaps and test color-space settings against a reference render;
- set explicit budgets for download size, decoded texture memory, triangle count and draw calls.
Texture memory is often the surprise. A compressed 2 MB image can occupy tens of megabytes after decoding on the GPU. Four 2048×2048 RGBA textures already require roughly 64 MB before mipmaps and intermediate render targets.
Three.js supports Draco, Meshopt and KTX2 in its web asset pipeline, but do not assume every compression path works unchanged in Expo GL. Its current context still leaves several compressed-texture methods unimplemented. Verify the exact loader, device and build before committing the art pipeline to a format.
Budget every frame
At 60 frames per second, the application has about 16.7 milliseconds for input, JavaScript, layout and rendering. A 3D view does not receive that entire budget. It shares the device with navigation, gestures, network work and the rest of the screen.
Start with a quality tier that is deliberately modest:
- cap device pixel ratio instead of rendering every high-density pixel;
- prefer one or two simple lights and baked shadows;
- avoid real-time reflections and large post-processing chains;
- reuse geometry and materials, and instance repeated objects;
- move per-frame values through refs and
useFrame, not React state; - use on-demand rendering for scenes that change only after interaction;
- pause or unmount the scene when it is fully off-screen.
Measure draw calls, triangles, texture allocation and frame time on a physical low-tier Android device and an older supported iPhone. Development mode adds overhead, so performance decisions need a release build. Expo GL also requires on-device JavaScript execution; legacy remote debugging moves JavaScript to the computer and does not represent the real rendering environment.
Design loading and failure as part of the scene
A 3D canvas should never be the only route to essential information. Show a designed placeholder while assets load, report meaningful progress, and provide a static image or simplified model when loading fails or the device cannot hold the full scene comfortably.
Cache versioned assets, but keep a way to invalidate them. Dispose geometries, materials and textures when a scene is permanently removed. If navigation keeps several screens mounted, make sure it is not also keeping several GL contexts and copies of the same textures alive.
Touch interaction deserves the same restraint as the visuals. One-finger orbit, pinch zoom and a clear reset action are often enough. Define gesture ownership when the canvas sits inside a scroll view so the scene does not steal every swipe.
Know when to leave this stack
React Three Fiber is a strong fit for product viewers, educational scenes, lightweight games and data-driven 3D that benefits from React composition. It is less convincing when the entire product depends on high-end native graphics, large worlds, advanced physics or deep ARKit and ARCore integration.
In those cases, evaluate a native engine or platform renderer before the content pipeline becomes expensive to move. A beautiful prototype is not proof that the architecture has enough headroom.
For most mobile 3D features, the winning approach is smaller: one focused scene, one carefully optimized asset, a real performance budget and a graceful 2D fallback. Make that reliable first. Then earn the next shader.