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:
104
README.md
104
README.md
@@ -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
|
||||
|
||||
47
deploy.sh
47
deploy.sh
@@ -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."
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user