•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
Building a 3D Portfolio with Three.js and Next.js

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:

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:

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:

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

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:

'use client'

Loading States

Implemented a skeleton loader for the initial render:

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:

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:

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 Live Demo: MNakhae.ir

If you found this article helpful or have questions about the implementation, feel free to reach out on LinkedIn or email me.

Mohammad Nakhaei Ramezanzadeh (محمد نخعی رمضان‌زاده) — Full Stack Developer & AI Engineer based in Tehran, Iran

Mohammad Nakhaei

Full Stack Developer & AI Engineer based in Tehran, Iran. Building intelligent and responsive web applications.