2024-12-018 min readWeb 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: ```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).
MN

Mohammad Nakhaei

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