Interactive 3D developer portfolio with a keyboard where every keycap is a skill. Built with Next.js, React, TypeScript, GSAP & Motion. Free to use!
See the codeA developer portfolio you can actually play with. Press keys on a 3D keyboard, watch other visitors' cursors fly by, and chat with whoever's online.

Free to use! This portfolio is open source. If you use it, a credit/link back would be really appreciated π
| Layer | Technologies |
|---|---|
| Framework | Next.js 16.2.2, React 19.2.4, TypeScript |
| Styling | Tailwind CSS, Shadcn UI, Aceternity UI |
| Animation | GSAP, Framer Motion |
| 3D | @chibi3d/runtime, Three.js, React Three Fiber |
| Resend | |
| Misc | Lenis (smooth scroll), Zod, next-themes |
Clone the repository:
git clone https://github.com/Naresh-Khatri/3d-portfolio.git
cd 3d-portfolio
Install dependencies:
pnpm install
Set up environment variables:
Copy .env.example to .env.local and fill in the values:
cp .env.example .env.local
| Variable | Required | Description |
|---|---|---|
RESEND_API_KEY | Yes | API key from Resend for the contact form |
NEXT_PUBLIC_WS_URL | No | WebSocket server URL for realtime features (cursors, chat, presence) |
UMAMI_DOMAIN | No | Umami analytics script URL |
UMAMI_SITE_ID | No | Umami website ID |
Run the development server:
pnpm dev
Open http://localhost:3000 and see the magic β¨
Run these before opening a pull request:
pnpm lint
pnpm typecheck
Next.js 16 no longer runs lint during builds. The ESLint CLI uses eslint.config.mjs. New React Compiler diagnostics for refs, immutability, effect state updates, and manual memoization report warnings while existing animation and realtime components are migrated. Hook ordering violations remain errors.
Blog frontmatter requires a title, summary, and publishedAt date in YYYY-MM-DD format. Optional fields are image, author, and a list of tags. Invalid frontmatter raises a content error; unknown blog slugs return a 404.
Files under public/assets can be replaced at the same URL and cache for one hour before revalidation. Next.js content-hashed static assets retain immutable caching.
All personal info is centralized in src/data/config.ts. Edit this single file to rebrand the portfolio:
const config = {
title: "Your Name | Your Title",
description: {
long: "Your long description for SEO...",
short: "Your short description...",
},
keywords: ["your", "keywords"],
author: "Your Name",
email: "[email protected]",
site: "https://yoursite.com",
// GitHub stars button in the header
githubUsername: "your-github-username",
githubRepo: "your-repo-name",
social: {
twitter: "https://x.com/you",
linkedin: "https://linkedin.com/in/you",
instagram: "https://instagram.com/you",
facebook: "https://facebook.com/you",
github: "https://github.com/you",
},
};
Other files you'll want to customize:
| File | What to change |
|---|---|
src/data/projects.tsx | Your projects, screenshots, descriptions, and tech stacks |
src/data/constants.ts | Skills list (name, description, icon) and work experience |
public/assets/ | Your images, OG image, and project screenshots |
Try colors, logos, and layouts in the Chibi keycap customizer, then apply your choices to SKILLS in src/data/constants.ts:
keyboardColor: keycap color, such as "#ff9408".keyboardIcon: copy an SVG URL from thesvg.org and paste it here. You can also use a local file under public/assets/keyboard/logos/.label and shortDescription: text shown when interacting with a key.shortcut: optional physical key code, such as "KeyQ". Keep shortcuts unique; defaults follow QWERTY letter order.The demo accepts logo names too, but keyboardIcon needs an image URL or local asset path. Without keyboard overrides, keys use the skill's color and icon.
Keys follow the order of SKILLS. To change the default six columns, pass a second argument to createKeyboardScene in keyboard-scene.tsx. Rows adjust automatically to the skill count.
The portfolio supports optional realtime features powered by a separate backend API:
These features activate automatically when the NEXT_PUBLIC_WS_URL environment variable is set. Without it, the portfolio works perfectly fine as a static site β no realtime features, no backend dependency.
[!NOTE] The backend API is not open source. This is intentional! Too many people have cloned the portfolio and claimed they built it from scratch. The realtime server stays private to keep the live experience unique and make it stand out.
This site is deployed on Vercel. To deploy your own:
If you'd like to contribute or suggest improvements, feel free to open an issue or submit a pull request. All contributions are welcome!
This project is open source and available under the MIT License.
If you use this portfolio, a credit or link back to the original repo would be much appreciated β€οΈ
Note on analytics: a deployed copy reports its own hostname once per browser (nothing else β no visitor, page, or referrer data) so I can see where the template gets used.
892 followers Β· starred Jan 2025
285 followers Β· starred Jul 2025
80 followers Β· starred Jul 2025
16 followers Β· starred Jan 2026
Interactive 3D developer portfolio with a keyboard where every keycap is a skill. Built with Next.js, React, TypeScript, GSAP & Motion. Free to use!
See the codeA developer portfolio you can actually play with. Press keys on a 3D keyboard, watch other visitors' cursors fly by, and chat with whoever's online.

Free to use! This portfolio is open source. If you use it, a credit/link back would be really appreciated π
| Layer | Technologies |
|---|---|
| Framework | Next.js 16.2.2, React 19.2.4, TypeScript |
| Styling | Tailwind CSS, Shadcn UI, Aceternity UI |
| Animation | GSAP, Framer Motion |
| 3D | @chibi3d/runtime, Three.js, React Three Fiber |
| Resend | |
| Misc | Lenis (smooth scroll), Zod, next-themes |
Clone the repository:
git clone https://github.com/Naresh-Khatri/3d-portfolio.git
cd 3d-portfolio
Install dependencies:
pnpm install
Set up environment variables:
Copy .env.example to .env.local and fill in the values:
cp .env.example .env.local
| Variable | Required | Description |
|---|---|---|
RESEND_API_KEY | Yes | API key from Resend for the contact form |
NEXT_PUBLIC_WS_URL | No | WebSocket server URL for realtime features (cursors, chat, presence) |
UMAMI_DOMAIN | No | Umami analytics script URL |
UMAMI_SITE_ID | No | Umami website ID |
Run the development server:
pnpm dev
Open http://localhost:3000 and see the magic β¨
Run these before opening a pull request:
pnpm lint
pnpm typecheck
Next.js 16 no longer runs lint during builds. The ESLint CLI uses eslint.config.mjs. New React Compiler diagnostics for refs, immutability, effect state updates, and manual memoization report warnings while existing animation and realtime components are migrated. Hook ordering violations remain errors.
Blog frontmatter requires a title, summary, and publishedAt date in YYYY-MM-DD format. Optional fields are image, author, and a list of tags. Invalid frontmatter raises a content error; unknown blog slugs return a 404.
Files under public/assets can be replaced at the same URL and cache for one hour before revalidation. Next.js content-hashed static assets retain immutable caching.
All personal info is centralized in src/data/config.ts. Edit this single file to rebrand the portfolio:
const config = {
title: "Your Name | Your Title",
description: {
long: "Your long description for SEO...",
short: "Your short description...",
},
keywords: ["your", "keywords"],
author: "Your Name",
email: "[email protected]",
site: "https://yoursite.com",
// GitHub stars button in the header
githubUsername: "your-github-username",
githubRepo: "your-repo-name",
social: {
twitter: "https://x.com/you",
linkedin: "https://linkedin.com/in/you",
instagram: "https://instagram.com/you",
facebook: "https://facebook.com/you",
github: "https://github.com/you",
},
};
Other files you'll want to customize:
| File | What to change |
|---|---|
src/data/projects.tsx | Your projects, screenshots, descriptions, and tech stacks |
src/data/constants.ts | Skills list (name, description, icon) and work experience |
public/assets/ | Your images, OG image, and project screenshots |
Try colors, logos, and layouts in the Chibi keycap customizer, then apply your choices to SKILLS in src/data/constants.ts:
keyboardColor: keycap color, such as "#ff9408".keyboardIcon: copy an SVG URL from thesvg.org and paste it here. You can also use a local file under public/assets/keyboard/logos/.label and shortDescription: text shown when interacting with a key.shortcut: optional physical key code, such as "KeyQ". Keep shortcuts unique; defaults follow QWERTY letter order.The demo accepts logo names too, but keyboardIcon needs an image URL or local asset path. Without keyboard overrides, keys use the skill's color and icon.
Keys follow the order of SKILLS. To change the default six columns, pass a second argument to createKeyboardScene in keyboard-scene.tsx. Rows adjust automatically to the skill count.
The portfolio supports optional realtime features powered by a separate backend API:
These features activate automatically when the NEXT_PUBLIC_WS_URL environment variable is set. Without it, the portfolio works perfectly fine as a static site β no realtime features, no backend dependency.
[!NOTE] The backend API is not open source. This is intentional! Too many people have cloned the portfolio and claimed they built it from scratch. The realtime server stays private to keep the live experience unique and make it stand out.
This site is deployed on Vercel. To deploy your own:
If you'd like to contribute or suggest improvements, feel free to open an issue or submit a pull request. All contributions are welcome!
This project is open source and available under the MIT License.
If you use this portfolio, a credit or link back to the original repo would be much appreciated β€οΈ
Note on analytics: a deployed copy reports its own hostname once per browser (nothing else β no visitor, page, or referrer data) so I can see where the template gets used.
892 followers Β· starred Jan 2025
285 followers Β· starred Jul 2025
80 followers Β· starred Jul 2025
16 followers Β· starred Jan 2026