In this comprehensive guide, I'll walk you through the complete process of building an interactive 3D portfolio using Three.js and Next.js 15. We'll cover everything from initial planning and setup to implementing custom GLSL shaders and deploying to production.
## The Vision Behind the Project
When I set out to build my portfolio, I wanted something that would immediately capture attention while still being professional and performant. The traditional portfolio sites felt too static, and I wanted to showcase my technical abilities through the portfolio itself.
After researching various approaches, I decided on a 3D animated sphere as the hero element. It would need to be:
- Visually striking but not overwhelming
- Performant across devices
- Responsive to user interaction
- Subtle enough not to distract from content
The challenge was creating something that felt alive without sacrificing the clean, minimalist aesthetic I was going for.
## Technical Stack and Architecture
The project is built with a carefully chosen tech stack:
**Core Framework**
- **Next.js 15**: Latest version with App Router for optimal performance and SEO
- **React 19**: Taking advantage of the newest React features
- **TypeScript**: For type safety throughout the application
**3D Graphics**
- **Three.js**: The industry-standard WebGL library
- **React Three Fiber**: React renderer for Three.js
- **@react-three/drei**: Useful helpers and abstractions
**Animation and Interaction**
- **GLSL Shaders**: Custom vertex and fragment shaders for unique effects
- **Framer Motion**: For page transitions and scroll-based animations
- **Lenis**: For smooth scrolling experience
## Setting Up the Development Environment
First, let's initialize a new Next.js project with TypeScript:
```bash
pnpm create next-app@latest portfolio --typescript --tailwind --app
cd portfolio
pnpm add three @react-three/fiber @react-three/drei
pnpm add -D @types/three
```
The project structure I settled on:
```
app/
layout.tsx
page.tsx
components/
sentient-sphere.tsx
smooth-scroll.tsx
lib/
utils.ts
```
## Creating the 3D Sphere Component
The heart of the project is the animated sphere. Here's how I built it step by step:
### Step 1: Basic Three.js Setup
I started with a simple sphere to ensure everything was working:
```typescript
import { Canvas } from '@react-three/fiber'
export function SentientSphere() {
return (
<Canvas camera={{ position: [0, 0, 5], fov: 45 }}>
<ambientLight intensity={0.5} />
<mesh>
<icosahedronGeometry args={[1.8, 64]} />
<meshBasicMaterial wireframe color="white" />
</mesh>
</Canvas>
)
}
```
### Step 2: Adding Custom Shaders
To create the organic, flowing animation, I implemented custom GLSL shaders. The vertex shader uses 3D Perlin noise to displace vertices:
**Vertex Shader Highlights:**
- Perlin noise function for organic displacement
- Time-based animation
- Normal-based displacement direction
**Fragment Shader Highlights:**
- Procedural wireframe effect
- Displacement-based intensity variation
- Transparency for depth
The shaders create a living, breathing effect that responds naturally to time progression.
### Step 3: Mouse Interaction
To make the sphere respond to user input, I added mouse tracking:
```typescript
const { pointer } = useThree()
useFrame((state, delta) => {
if (meshRef.current) {
meshRef.current.rotation.y += delta * 0.05
meshRef.current.rotation.x = MathUtils.lerp(
meshRef.current.rotation.x,
pointer.y * 0.2,
0.05
)
}
})
```
The lerp (linear interpolation) creates smooth, natural movement as the sphere follows the cursor.
## Performance Optimization Strategies
Performance was critical. Here's what I did to maintain 60fps:
### 1. Geometry Optimization
- Used icosahedron instead of sphere (fewer vertices)
- Set subdivision to 64 (balance between smoothness and performance)
- Considered reducing to 32 on mobile devices
### 2. Shader Efficiency
- Minimized texture lookups
- Used built-in GLSL functions where possible
- Avoided expensive operations in fragment shader
### 3. React Optimization
- Used proper disposal of Three.js objects
- Implemented loading states
- Lazy loaded the Canvas component
### 4. Device-Specific Adjustments
```typescript
const isMobile = window.matchMedia('(max-width: 768px)').matches
const subdivisions = isMobile ? 32 : 64
```
## Integrating with Next.js
Next.js 15's App Router required some specific considerations:
### Client Components
Since Three.js needs browser APIs, I marked the component as a client component:
```typescript
'use client'
```
### Loading States
Implemented a skeleton loader for the initial render:
```typescript
if (!mounted) {
return <div className="loading-skeleton" />
}
```
### SEO Considerations
Even with client-side 3D graphics, the rest of the page benefits from Next.js SSR for optimal SEO.
## Smooth Scrolling Integration
To enhance the overall experience, I integrated Lenis for butter-smooth scrolling:
```typescript
import { ReactLenis } from 'lenis/react'
export function SmoothScroll({ children }) {
return (
<ReactLenis root options={{ lerp: 0.1, duration: 1.2 }}>
{children}
</ReactLenis>
)
}
```
The sphere's opacity fades as you scroll, creating a smooth transition to the content below.
## Deployment and Production Considerations
### Build Configuration
Updated next.config.js for optimal Three.js bundling:
```javascript
const nextConfig = {
images: { unoptimized: true },
typescript: { ignoreBuildErrors: false }
}
```
### Vercel Deployment
The site deploys seamlessly to Vercel with:
- Automatic HTTPS
- Edge caching for static assets
- Optimal compression
## Lessons Learned
After building this project, here are my key takeaways:
1. **Start Simple**: Begin with basic Three.js setup before adding complexity
2. **Performance First**: Profile early and often with Chrome DevTools
3. **Mobile Matters**: Test on real devices, not just DevTools responsive mode
4. **Shader Learning Curve**: GLSL takes time, but the results are worth it
5. **Balance Aesthetics**: Cool effects shouldn't compromise usability
## Future Improvements
Things I'm considering for future iterations:
- Add post-processing effects (bloom, depth of field)
- Implement mouse trail particles
- Add color scheme toggles
- Create more interactive 3D elements
## Conclusion
Building a 3D portfolio with Three.js and Next.js is an excellent way to showcase technical skills while creating a memorable user experience. The key is finding the right balance between visual impact and performance.
The combination of Next.js 15's modern architecture with Three.js's powerful 3D capabilities creates endless possibilities for creative web experiences. Whether you're building a portfolio, a product showcase, or an interactive experience, this tech stack provides the foundation for something truly unique.
Remember: the best portfolio is one that not only looks impressive but also performs flawlessly and provides genuine value to your visitors.
**GitHub Repository**: [View the source code](https://github.com/MNakhaeiR/MNakhae.iR)
**Live Demo**: [MNakhae.ir](https://MNakhae.ir)
If you found this article helpful or have questions about the implementation, feel free to reach out on [LinkedIn](https://linkedin.com/in/MNakhaeiR) or [email me](mailto:m.nakhaei.r3@gmail.com).
2024-12-01•8 min read•Web Development
Building a 3D Portfolio with Three.js and Next.js
Learn how I created an interactive 3D portfolio using Three.js, custom GLSL shaders, and Next.js 15. From concept to deployment.
Three.jsNext.jsGLSL3DWebGL
MN
Mohammad Nakhaei
Full Stack Developer & AI Engineer based in Tehran, Iran. Building intelligent and responsive web applications.