How to set up Three.js with Next.js and TypeScript in 2025
How to Set Up Three.js with Next.js and TypeScript in 2025
Building interactive 3D web experiences requires more than just throwing Three.js into your project. When combining Three.js with Next.js and TypeScript, you'll encounter module resolution quirks, server-side rendering gotchas, and canvas initialization timing issues. This guide walks you through a production-ready setup that avoids common pitfalls.
Why Three.js + Next.js?
Three.js is the go-to JavaScript 3D library for cross-browser WebGL and WebGPU rendering. Next.js provides modern React patterns, optimized bundling, and API routes. Combining them gives you:
- SSR-safe 3D rendering (canvas only initializes client-side)
- TypeScript type safety for geometries, materials, and cameras
- Optimized builds with Next.js's tree-shaking and code splitting
- Fast refresh for iterating on 3D scenes during development
Prerequisites
- Node.js 18+ installed
- Basic knowledge of React and Next.js
- Familiarity with ES6 imports
Step 1: Create a Next.js Project with TypeScript
Start with a fresh Next.js installation:
npx create-next-app@latest my-threejs-app --typescript --tailwind
cd my-threejs-app
When prompted, select:
- ESLint: Yes
- src/ directory: Yes (optional but recommended)
- App Router: Yes
Step 2: Install Three.js and Types
Three.js ships as an ESM module on npm, making it Next.js-compatible by default:
npm install three
npm install --save-dev @types/three
Verify installation:
npm list three
# three@r128 (or latest version)
Step 3: Create a Client-Side 3D Component
Since Three.js requires a browser DOM, wrap it in a client component. Create src/components/Canvas3D.tsx:
'use client';
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
export default function Canvas3D() {
const containerRef = useRef<HTMLDivElement>(null);
const sceneRef = useRef<THREE.Scene | null>(null);
const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
useEffect(() => {
if (!containerRef.current) return;
// Scene setup
const width = window.innerWidth;
const height = window.innerHeight;
const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10);
camera.position.z = 1;
const scene = new THREE.Scene();
sceneRef.current = scene;
// Create a rotating cube
const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
const material = new THREE.MeshNormalMaterial();
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// WebGL renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
rendererRef.current = renderer;
containerRef.current.appendChild(renderer.domElement);
// Animation loop
const animate = (time: number) => {
mesh.rotation.x = time / 2000;
mesh.rotation.y = time / 1000;
renderer.render(scene, camera);
};
renderer.setAnimationLoop(animate);
// Handle resize
const handleResize = () => {
const w = window.innerWidth;
const h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
};
window.addEventListener('resize', handleResize);
// Cleanup
return () => {
window.removeEventListener('resize', handleResize);
renderer.dispose();
geometry.dispose();
material.dispose();
containerRef.current?.removeChild(renderer.domElement);
};
}, []);
return <div ref={containerRef} className="w-full h-screen" />;
}
Step 4: Use the Component in Your Page
Update src/app/page.tsx:
import Canvas3D from '@/components/Canvas3D';
export default function Home() {
return (
<main className="w-full h-screen">
<Canvas3D />
</main>
);
}
Step 5: Verify Your Build
Start the development server:
npm run dev
Open http://localhost:3000 in your browser. You should see a rotating cube that responds to window resizing.
For production, verify bundle size:
npm run build
Three.js typically adds ~600KB gzipped, depending on which modules you import.
Common Configuration Issues
Issue: "Module not found: three"
Solution: Ensure you're in a client component ('use client' directive). Three.js requires browser APIs and cannot run server-side.
Issue: Canvas doesn't resize on window resize
Solution: Always update the camera's aspect ratio and call updateProjectionMatrix() when the window resizes, as shown in the example above.
Issue: Memory leaks or poor performance
Solution: Call .dispose() on geometries, materials, and renderers in cleanup functions. This frees WebGL resources.
Advanced Setup: Loaders and Assets
For GLTF models, use Three.js's built-in loader:
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
const loader = new GLTFLoader();
loader.load('/model.glb', (gltf) => {
scene.add(gltf.scene);
});
Place your 3D assets in public/ for Next.js to serve them statically.
TypeScript Tips
Always type your refs and state:
const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
This catches type mismatches at build time, not runtime.
Next Steps
- Explore Three.js examples for inspiration
- Learn about materials:
MeshStandardMaterial,MeshPhongMaterialfor realistic lighting - Add controls with
OrbitControlsfromthree/examples/jsm/controls/OrbitControls - Consider using Drei (React Three Fiber) for a more React-friendly API
Your Three.js + Next.js + TypeScript stack is now production-ready. Happy rendering!