In today’s competitive tech landscape, a developer’s portfolio is more than just a list of projects; it’s a personal brand statement and a direct demonstration of skill. A static, text-based resume or a simple grid of projects no longer cuts through the noise. To truly capture attention, developers are turning to immersive, interactive web experiences. This is where the power of 3D graphics on the web comes into play, and at the forefront of this revolution is Three.js, a versatile and powerful JavaScript library for creating and displaying 3D computer graphics in a web browser.
The integration of libraries like Three.js with modern front-end frameworks has become a major topic in the latest React News and Next.js News. By combining the declarative, component-based architecture of frameworks like React (used by Next.js) with the rendering capabilities of Three.js, developers can build stunning, performant, and maintainable 3D websites. This article will serve as a comprehensive guide to leveraging this powerful combination to build a standout developer portfolio. We’ll explore the core concepts, dive into practical implementation with code examples, discuss advanced techniques for interactivity and visual flair, and cover best practices for optimization and responsiveness.
Understanding the Core Components of a 3D Web Experience
Before diving into complex integrations, it’s essential to grasp the fundamental building blocks of any Three.js application. Traditionally, this involves an imperative, vanilla JavaScript approach. However, the modern ecosystem, particularly within the React world, offers a more declarative and intuitive way to work with these concepts through libraries like React Three Fiber (R3F).
The Foundational Trio: Scene, Camera, and Renderer
At its heart, a Three.js application is composed of three essential components:
- Scene: Think of the scene as a virtual stage or universe where all your 3D objects, lights, and cameras will reside. It acts as the top-level container for everything you want to display.
- Camera: The camera is the viewer’s eye. It determines what part of the scene is visible and from what perspective. The most common type is the
PerspectiveCamera
, which mimics how the human eye sees, with objects in the distance appearing smaller. - Renderer: The renderer’s job is to take the scene and the camera’s viewpoint and “render” or draw the result onto the screen. Three.js uses WebGL to perform this rendering, harnessing the power of the user’s GPU for smooth performance.
Bringing Objects to Life: Geometry, Material, and Mesh
An empty scene isn’t very interesting. To populate it, you need objects. In Three.js, a visible object, known as a Mesh, is a combination of two things:
- Geometry: This defines the shape of the object. It’s a collection of vertices and faces that form a 3D model. Three.js provides many built-in geometries like
BoxGeometry
,SphereGeometry
, andTorusGeometry
. - Material: This defines the appearance of the object’s surface. It controls properties like color, texture, shininess, and transparency. A common material for non-shiny surfaces is
MeshStandardMaterial
.
The React Three Fiber (R3F) Abstraction
React Three Fiber provides a React renderer for Three.js. This means you can build your 3D scene using familiar React components, hooks, and practices. It translates JSX into Three.js objects, managing the scene graph, rendering loop, and event handling for you. This approach is a game-changer, as discussed in many recent Svelte News and Vue.js News articles exploring similar declarative UI patterns.
Here’s a basic example of a rotating cube using R3F. This code would live inside a React component.
import React, { useRef } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
function SpinningCube() {
// useRef is used to get a direct reference to the mesh
const meshRef = useRef();
// useFrame is a hook that runs on every rendered frame
useFrame((state, delta) => {
if (meshRef.current) {
meshRef.current.rotation.x += delta;
meshRef.current.rotation.y += delta;
}
});
return (
<mesh ref={meshRef} scale={1.5}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={'orange'} />
</mesh>
);
}
export default function App() {
return (
<Canvas>
<ambientLight intensity={0.5} />
<directionalLight position={[0, 10, 5]} intensity={1} />
<SpinningCube />
</Canvas>
);
}
Implementing a 3D Portfolio Scene in Next.js
Now that we understand the basics, let’s integrate a 3D scene into a Next.js project. Next.js is an excellent choice due to its powerful features like server-side rendering, static site generation, and optimized image and script loading, which are hot topics in Remix News and general web development discussions.

Project Setup and Dependencies
First, create a new Next.js application and install the necessary Three.js and R3F packages.
npx create-next-app@latest my-3d-portfolio
cd my-3d-portfolio
npm install three @react-three/fiber @react-three/drei
The @react-three/drei
library is a collection of useful helpers and abstractions for R3F. It provides pre-built components for controls, loaders, effects, and more, saving you a significant amount of boilerplate code.
Creating a Dedicated 3D Component
It’s best practice to encapsulate your 3D scene in its own component. This keeps your code organized and allows for powerful optimizations like lazy loading. For a portfolio, you might want to display a custom 3D model of a laptop, a logo, or an abstract representation of your skills.
The most common format for 3D models on the web is glTF (.gltf
or .glb
). The drei
library provides the useGLTF
hook to easily load these models.
Let’s create a component that loads and displays a 3D model. You would place your model.glb
file in the /public
directory of your Next.js project.
import React from 'react';
import { useGLTF, OrbitControls } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';
// Component that loads and displays the 3D model
function ModelViewer() {
// useGLTF hook preloads and caches the model
const { scene } = useGLTF('/laptop_model.glb');
// We use <primitive> to render the loaded scene directly
// We can also adjust its position, scale, etc.
return <primitive object={scene} scale={0.5} position={[0, -1, 0]} />;
}
// The main component that sets up the Canvas and environment
export default function PortfolioScene() {
return (
<div style={{ width: '100%', height: '100vh' }}>
<Canvas camera={{ position: [0, 2, 5], fov: 75 }}>
<ambientLight intensity={0.8} />
<hemisphereLight intensity={0.5} groundColor="black" />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
<React.Suspense fallback={null}>
<ModelViewer />
</React.Suspense>
<OrbitControls enableZoom={false} />
</Canvas>
</div>
);
}
In this example, we’ve created a PortfolioScene
component. It sets up a Canvas
, adds some basic lighting for better visibility, and includes OrbitControls
to allow the user to rotate around the model. We wrap our ModelViewer
in React.Suspense
because model loading is an asynchronous operation. This ensures a fallback can be shown while the model is being fetched, preventing application crashes.
Advanced Techniques: Animation, Interaction, and Visual Polish
A static 3D model is impressive, but adding animation and interactivity is what truly creates a memorable experience. This is where you can showcase your creativity and attention to detail. The ecosystem around R3F makes these advanced techniques surprisingly accessible, a trend also seen in the evolution of tools discussed in Vite News and Webpack News, which focus on improving developer experience.
Animating Based on User Scroll
A popular effect in modern web design is to animate a 3D scene as the user scrolls down the page. This can be used to tell a story, reveal different parts of a model, or simply create a dynamic background. We can achieve this by using the useScroll
helper from drei
and the useFrame
hook.
Imagine your portfolio page has text sections, and you want your 3D model to rotate and move as the user scrolls past them.

import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { useGLTF, useScroll, ScrollControls } from '@react-three/drei';
function AnimatedModel() {
const modelRef = useRef();
const { scene } = useGLTF('/rocket.glb');
// The useScroll hook gives us data about the scroll position
const scroll = useScroll();
useFrame((state, delta) => {
// scroll.offset gives a value from 0 to 1
const offset = scroll.offset;
// Animate rotation based on scroll
modelRef.current.rotation.y = offset * Math.PI * 2;
// Animate position based on scroll
modelRef.current.position.x = offset * -5;
});
return <primitive ref={modelRef} object={scene} scale={0.8} />;
}
// Wrap your canvas with ScrollControls
export default function ScrollingExperience() {
return (
<Canvas>
<ambientLight />
<ScrollControls pages={3} damping={0.25}>
<AnimatedModel />
</ScrollControls>
</Canvas>
);
}
Here, the ScrollControls
component from drei
creates a scrollable container. The useScroll
hook then provides the current scroll offset within the useFrame
loop, allowing us to link any property of our 3D model (like rotation or position) directly to the user’s scroll progress.
Adding Post-Processing Effects
Post-processing can elevate your scene from looking like a basic 3D render to a cinematic experience. Effects like bloom (a soft glow around bright objects), depth of field, and ambient occlusion can add a layer of realism and polish. The @react-three/postprocessing
library makes this easy to implement.
You can wrap your scene components inside an <EffectComposer>
and add the desired effects as child components. This modular approach is common in modern development, echoing principles seen in backend frameworks like NestJS News and AdonisJS News where middleware and modules are composed together.
Best Practices for Performance and Optimization
While creating visually stunning 3D scenes is exciting, performance is paramount. A portfolio that takes too long to load or runs poorly on less powerful devices will leave a negative impression. The latest Node.js News and Bun News often highlight performance as a key feature, and the same discipline should be applied to the front end.
Model and Asset Optimization
- Polygon Count: Keep your 3D models as low-poly as possible without sacrificing visual quality. Tools like Blender have decimate modifiers to reduce polygon count.
- Texture Size: Use compressed texture formats (like WebP) and ensure dimensions are powers of two (e.g., 1024×1024).
- Draco Compression: Use Draco, a library from Google, to compress the geometry of your glTF models. The
useGLTF
hook indrei
handles Draco-compressed models automatically if you configure it correctly.
Lazy Loading and Code Splitting
Three.js and 3D models can be heavy. You should not block the initial render of your portfolio page waiting for them to load. Next.js’s dynamic imports are perfect for this.
import dynamic from 'next/dynamic';
import { Suspense } from 'react';
// Dynamically import the 3D scene component with SSR turned off
const PortfolioScene = dynamic(() => import('../components/PortfolioScene'), {
ssr: false,
suspense: true,
});
export default function HomePage() {
return (
<main>
<h1>Welcome to My Portfolio</h1>
<p>Here are my skills and projects...</p>
<Suspense fallback={`<div>Loading 3D experience...</div>`}>
<PortfolioScene />
</Suspense>
</main>
);
}
By using next/dynamic
with ssr: false
, we ensure the heavy 3D component is only loaded on the client-side and doesn’t impact the server-side render time. The Suspense
boundary allows us to show a loading state while the component and its assets are being fetched.
Performance Monitoring and Adaptation
The drei
library offers helpers to adapt the experience to the user’s device capabilities.
<AdaptiveDpr>
: Automatically adjusts the device pixel ratio based on the frame rate, improving performance on slower devices.<PerformanceMonitor>
: Can be used to dynamically adjust the quality of your scene. For example, you could disable shadows or post-processing effects if the frame rate drops below a certain threshold.
Conclusion: Crafting Your Interactive Business Card
Integrating Three.js into a Next.js portfolio is no longer a niche or overly complex task. Thanks to the powerful abstractions provided by libraries like React Three Fiber and Drei, developers can create rich, interactive 3D experiences using the same declarative, component-based patterns they already know and love. This combination transforms a portfolio from a static document into an engaging and memorable interactive business card.
The key takeaways are to start with a solid understanding of the core 3D concepts, leverage the R3F ecosystem to simplify development, add layers of interactivity and visual polish to stand out, and always prioritize performance through optimization and lazy loading. By embracing these techniques, you can build a portfolio that not only lists your skills but actively demonstrates your creativity, technical prowess, and commitment to crafting exceptional user experiences. The web is a canvas—it’s time to start painting in three dimensions.