A modern, full-stack developer portfolio built with Next.js 16, React 19, and Tailwind CSS v4. This project showcases my skills, projects, and thoughts through a clean, dark-themed interface with smooth animations and robust backend integration.
Live Link portfolio.thaiduy.digital
- Framework: Next.js 16 (App Directory)
- Language: TypeScript
- Styling: Tailwind CSS v4
- Database: MongoDB with Prisma ORM
- Authentication: Better Auth
- Animations: Framer Motion
- UI Components: Radix UI / Shadcn-like Implementation
- Icons: Lucide React
- Simple Icons : simpleicons
- Content Management: Custom Blog & Project System
- Integrations:
- Telegram Bot (Contact Form)
- Cloudflare R2 (Image Hosting)
- Wakatime (Coding Stats)
- Dynamic Content: CMS-like features for managing Projects and Blog posts.
- Admin Dashboard: Secure admin area for content management.
- Interactive UI: Smooth transitions, scroll animations, and a polished dark mode aesthetic.
- Contact Form: Direct integration with Telegram for instant message delivery.
- Real-time Stats: Live coding statistics via Wakatime API.
- Dynamic Audio Waveform: An intelligent SVG visualizer integrated into the navbar that reflects your current listening activity.
- SEO Optimized: Built-in metadata and Open Graph support.
The WaveformLine component isn't just a static animation; it's a data-driven visualizer that synchronizes with your Last.fm/YouTube Music status:
- Genre-Aware Animation: It parses the current track's metadata (title and artist) to "guess" the music genre using an internal keyword mapping system.
- Adaptive Physics: Depending on the detected genre, the waveform automatically adjusts:
- Rock/Metal: High amplitude and high frequency for an energetic look.
- Pop/Dance: Standard rhythmic pulse.
- Lo-fi/Jazz/Ambient: Low amplitude and slow speed for a calm, relaxing vibe.
- Classical: Precise, high-frequency waves with moderate speed.
- Smooth Transitions: Built with Framer Motion, the waveform gracefully morphs between different states when the music genre changes.
- Real-time Connectivity: Fetches live "Now Playing" data to ensure the visualizer stays in sync with your actual activity.
| Skills | Blog |
|---|---|
![]() |
![]() |
- Node.js (v18+ recommended)
- MongoDB Database or MongoDB Atlas at here (Cloud MongoDB)
- Cloudflare R2 Storage Account
- Telegram Bot Token (optional, for contact form)
-
Clone the repository:
git clone https://github.com/thdangduy/portfolio.git cd portfolio -
Install dependencies:
npm install # or pnpm install # or bun install (recommended)
-
Environment Setup:
Rename
.env.exampleto.envand fill in your secrets:cp .env.example .env
Required variables:
DATABASE_URL: MongoDB connection stringBETTER_AUTH_SECRET: Secret for authenticationADMIN_SECRET: Secret header for admin actionsTELEGRAM_BOT_TOKEN&TELEGRAM_CHAT_ID: For contact formCLOUDFLARE_R2_ENDPOINT: Cloudflare R2 endpointCLOUDFLARE_R2_BUCKET: R2 bucket nameCLOUDFLARE_R2_ACCESS_KEY_ID: R2 access key IDCLOUDFLARE_R2_SECRET_ACCESS_KEY: R2 secret keyNEXT_PUBLIC_CLOUDFLARE_R2_BASE_URL: Public R2 base URL for uploaded imagesLASTFM_API_KEY: Last.fm API keyLASTFM_USER: Last.fm usernameWAKATIME_API_KEY: Wakatime API key - change url at /portfolio/app/page.tsx
-
Database Setup:
Generate Prisma client and push schema:
npx prisma generate npx prisma db push
-
Run Development Server:
bun devnpm run devOpen http://localhost:3000 to view the site.
You can build and run the production app with Docker Compose.
-
Prepare environment variables:
cp .env.example .env
Fill in the required values in
.env. Docker Compose will load this file at runtime. -
Build and start the container:
docker compose up -d --build
-
Open the app:
Visit http://localhost:3000.
-
View logs:
docker compose logs -f portfolio
-
Stop the container:
docker compose down
The Compose service maps container port 3000 to host port 3000. If that port is already in use, change the ports value in docker-compose.yml.
/: Home Page (Intro, About, Skills, etc.)/projects: All Projects List/project/[slug]: Single Project Details/blog: Blog Posts/contact: Contact Page/login: Admin Login
/admin: a mini CMS Admin panel. You can add, edit, and delete projects, settings : title, seo....
GET /api/projects: Fetch all projectsPOST /api/projects: Create a new project (Admin)GET /api/blog/[slug]: Get a specific blog postPOST /api/contact: Send a contact message
/app: Next.js App Router pages and API routes./components: Reusable UI components./lib: Utility functions, Prisma client, and configurations./prisma: Database schema./public: Static assets.
// Built with ❤️ by Thai Duy. Inspired by Avisek Ray (biisal)
This project is licensed under the MIT License.






