feat: Upgrade to Next.js 16 and refactor deployment to use an external database with a simplified Docker Compose setup.

This commit is contained in:
Moh Dzulfikri Maulana
2026-03-09 03:05:39 +07:00
parent b8a5ce72c1
commit dca848666e
3 changed files with 61 additions and 117 deletions

104
README.md
View File

@@ -1,112 +1,100 @@
# Portfolio — Senior Fullstack Engineer
# Portfolio — Fullstack Developer
A modern, production-grade portfolio website built with Next.js 15, TailwindCSS, Framer Motion, and Prisma.
A modern, production-grade portfolio website built with Next.js 16, TailwindCSS, Framer Motion, and Prisma.
## Tech Stack
- **Frontend**: Next.js 15 (App Router), React 19, TypeScript
- **Frontend**: Next.js 16 (App Router), React 19, TypeScript
- **Styling**: TailwindCSS + custom design tokens
- **Animations**: Framer Motion
- **ORM**: Prisma (PostgreSQL)
- **Validation**: Zod + React Hook Form
- **Icons**: Lucide React + React Icons
- **Infrastructure**: Docker + Docker Compose
## Project Structure
```
src/
├── app/
│ ├── api/
│ └── contact/ # Contact form API route
│ ├── globals.css
│ ├── api/ # API Routes (Analytics, Contact, etc.)
├── blog/ # Blog pages
│ ├── dashboard/ # Admin dashboard
│ ├── layout.tsx # Root layout with SEO metadata
│ └── page.tsx # Home page (Server Component)
├── components/
│ ├── sections/ # Full-page section components
│ ├── Navbar.tsx
│ │ ├── HeroSection.tsx
│ │ ├── AboutSection.tsx
│ │ ├── TechStackSection.tsx
│ │ ├── ProjectsSection.tsx
│ │ ├── ExperienceSection.tsx
│ │ ├── ArchitectureSection.tsx
│ │ ├── ContactSection.tsx
│ │ └── Footer.tsx
│ └── ui/ # Reusable UI components
│ └── ProjectCard.tsx
└── ui/ # Reusable UI components (shadcn/ui style)
├── lib/
│ ├── prisma.ts # Prisma client singleton
── utils.ts # cn() utility
└── types/
└── index.ts # TypeScript types
prisma/
├── schema.prisma # Database schema
── seed.ts # Seed data
── s3.ts # Storage configuration (Cloudflare R2)
│ └── visitor.ts # Analytics tracking logic
├── prisma/
│ ├── schema.prisma # Database schema
│ └── migrations/ # Database migration history
── Dockerfile # Multi-stage production build
├── docker-compose.yml # Infrastructure orchestration
└── deploy.sh # Production deployment script
```
## Getting Started
### 1. Install dependencies
```bash
npm install
# Also install react-icons for tech stack icons
npm install react-icons
pnpm install
```
### 2. Setup environment
```bash
cp .env.example .env
# Edit .env with your DATABASE_URL
# Edit .env with your DATABASE_URL and other credentials
```
### 3. Setup database
```bash
npm run db:generate # Generate Prisma client
npm run db:push # Push schema to database
npx ts-node prisma/seed.ts # Seed with sample data
pnpm db:generate # Generate Prisma client
npx prisma migrate dev # Run migrations
```
### 4. Run development server
```bash
npm run dev
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000)
## Customization
## Deployment (Production Ready)
### Personal Information
Update these files with your information:
- `src/app/layout.tsx` — SEO metadata (name, description, URL)
- `src/components/sections/HeroSection.tsx` — Hero text & social links
- `src/components/sections/AboutSection.tsx` — Bio, stats, skills
- `src/components/sections/Footer.tsx` — Social links, email
- `src/components/sections/ContactSection.tsx` — Contact info
Project ini sudah dilengkapi dengan konfigurasi Docker untuk deployment mandiri (self-hosted).
### Database Content
After running seed, use Prisma Studio to manage content:
### Deploy menggunakan Docker
1. Pastikan Docker dan Docker Compose sudah terinstall di server.
2. Siapkan file `.env` di root direktori.
3. Jalankan script deployment:
```bash
npm run db:studio
chmod +x deploy.sh
./deploy.sh
```
Or update `prisma/seed.ts` with your actual projects and experience data.
Script `deploy.sh` akan secara otomatis:
- Mendeteksi apakah database sudah berjalan (mencegah konflik).
- Membangun image Docker terbaru (`fikri-portfolio-app`).
- Menjalankan migrasi database Prisma secara otomatis.
- Melakukan pembersihan image lama untuk menghemat ruang disk.
## Architecture Philosophy
Aplikasi akan berjalan di port **4000**.
This portfolio demonstrates the same architectural principles I apply in all projects:
- **Clean folder structure** — scalable and maintainable
- **Server Components** — data fetching at the server level with graceful fallbacks
- **Type safety** — full TypeScript throughout
- **Separation of concerns** — UI components separate from data fetching
- **Graceful degradation** — fallback data when DB is not connected
## Architecture & Philosophy
## Deployment
Portfolio ini merefleksikan pengalaman saya sebagai **Fullstack Developer selama 3+ tahun** dalam membangun aplikasi web yang skalabel dan siap produksi:
- **Scalable Architecture**: Pemisahan yang jelas antara UI, logika bisnis, dan akses data.
- **Modern Next.js Features**: Memanfaatkan Server Components untuk performa maksimal dan SEO.
- **Type Safety**: Penggunaan TypeScript di seluruh bagian aplikasi (End-to-end).
- **Automated Deployment**: Konfigurasi Docker yang dioptimalkan untuk performa dan kemudahan maintenance.
- **Clean Code**: Fokus pada keterbacaan kode (*readability*) dan kemudahan pemeliharaan (*maintainability*).
Deploy to Vercel (recommended):
## Contact
1. Push to GitHub
2. Import to Vercel
3. Add `DATABASE_URL` environment variable
4. Deploy
For PostgreSQL, use [Supabase](https://supabase.com) or [Neon](https://neon.tech) for free hosted PostgreSQL.
Update profile Anda di:
- `src/app/layout.tsx` — SEO metadata (name, description)
- `src/components/sections/...` — Konten section masing-masing

View File

@@ -1,51 +1,30 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status
# Berhenti jika ada error
set -e
echo "Starting Production Deployment..."
echo "🚀 Memulai Deployment Aplikasi (TANPA DATABASE)..."
# Load environment variables from .env if it exists
# Load environment variabel
if [ -f .env ]; then
echo "Loading environment variables from .env..."
echo "📄 Loading .env..."
export $(grep -v '^#' .env | xargs)
fi
# Check if 'docker compose' (v2) or 'docker-compose' (v1) is available
# Deteksi perintah docker compose
if docker compose version >/dev/null 2>&1; then
COMPOSE_CMD="docker compose"
elif docker-compose version >/dev/null 2>&1; then
else
COMPOSE_CMD="docker-compose"
else
echo "Error: Docker Compose not found. Please install Docker Compose first."
exit 1
fi
# Configuration
DB_CONTAINER_NAME="fikri-portfolio-db"
DB_PORT="5429"
echo "📦 Membangun dan menjalankan container aplikasi..."
# Langsung jalankan perintah docker-compose up
# --build memastikan image terbaru selalu dibuat
$COMPOSE_CMD up -d --build app
# Check if any container is using the DB port or if our specific container is running
EXISTING_DB=$(docker ps -q -f publish=${DB_PORT})
if [ ! -z "$EXISTING_DB" ]; then
echo "Database is already running on port ${DB_PORT}. Container ID: $EXISTING_DB"
echo "Building and updating only the application..."
# --build ensures we use the latest code
# --no-deps ensures we don't restart the DB if it's healthy
$COMPOSE_CMD up -d --build --no-deps app
else
echo "Database not found on port ${DB_PORT}."
echo "Full deployment (Database + App)..."
$COMPOSE_CMD up -d --build
fi
# Health check - optional but recommended
echo "Waiting for application to be healthy..."
# You could add a curl check here if desired
# Cleanup: remove dangling images to save disk space
echo "Cleaning up old images..."
# Membersihkan image sampah
echo "🧹 Membersihkan image lama..."
docker image prune -f
echo "Deployment script finished successfully!"
echo "Deployment Selesai! Aplikasi berjalan di port 4000."

View File

@@ -1,22 +1,4 @@
services:
db:
image: postgres:15-alpine
container_name: fikri-portfolio-db
restart: always
environment:
POSTGRES_USER: ${POSTGRES_USER:-fikri}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-fikri}
POSTGRES_DB: ${POSTGRES_DB:-fullstack-portfolio}
ports:
- "5429:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-fikri} -d ${POSTGRES_DB:-fullstack-portfolio}"]
interval: 5s
timeout: 5s
retries: 5
app:
build:
context: .
@@ -27,7 +9,8 @@ services:
ports:
- "4000:4000"
environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-fikri}:${POSTGRES_PASSWORD:-fikri}@host.docker.internal:5429/${POSTGRES_DB:-fullstack-portfolio}?schema=public
# Aplikasi akan langsung menggunakan DATABASE_URL dari file .env (pointing ke DB existing Anda)
DATABASE_URL: ${DATABASE_URL}
JWT_SECRET: ${JWT_SECRET}
R2_TOKEN: ${R2_TOKEN}
R2_ACCESS_KEY: ${R2_ACCESS_KEY}
@@ -35,13 +18,7 @@ services:
R2_ENDPOINT: ${R2_ENDPOINT}
R2_BUCKET_NAME: ${R2_BUCKET_NAME}
IMAGE_URL: ${IMAGE_URL}
depends_on:
db:
condition: service_healthy
extra_hosts:
- "host.docker.internal:host-gateway"
command: >
sh -c "npx prisma migrate deploy && node server.js"
volumes:
postgres_data: