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
|
## 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
|
- **Styling**: TailwindCSS + custom design tokens
|
||||||
- **Animations**: Framer Motion
|
- **Animations**: Framer Motion
|
||||||
- **ORM**: Prisma (PostgreSQL)
|
- **ORM**: Prisma (PostgreSQL)
|
||||||
- **Validation**: Zod + React Hook Form
|
- **Validation**: Zod + React Hook Form
|
||||||
- **Icons**: Lucide React + React Icons
|
- **Icons**: Lucide React + React Icons
|
||||||
|
- **Infrastructure**: Docker + Docker Compose
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── app/
|
├── app/
|
||||||
│ ├── api/
|
│ ├── api/ # API Routes (Analytics, Contact, etc.)
|
||||||
│ │ └── contact/ # Contact form API route
|
│ ├── blog/ # Blog pages
|
||||||
│ ├── globals.css
|
│ ├── dashboard/ # Admin dashboard
|
||||||
│ ├── layout.tsx # Root layout with SEO metadata
|
│ ├── layout.tsx # Root layout with SEO metadata
|
||||||
│ └── page.tsx # Home page (Server Component)
|
│ └── page.tsx # Home page (Server Component)
|
||||||
├── components/
|
├── components/
|
||||||
│ ├── sections/ # Full-page section components
|
│ ├── sections/ # Full-page section components
|
||||||
│ │ ├── Navbar.tsx
|
│ └── ui/ # Reusable UI components (shadcn/ui style)
|
||||||
│ │ ├── HeroSection.tsx
|
|
||||||
│ │ ├── AboutSection.tsx
|
|
||||||
│ │ ├── TechStackSection.tsx
|
|
||||||
│ │ ├── ProjectsSection.tsx
|
|
||||||
│ │ ├── ExperienceSection.tsx
|
|
||||||
│ │ ├── ArchitectureSection.tsx
|
|
||||||
│ │ ├── ContactSection.tsx
|
|
||||||
│ │ └── Footer.tsx
|
|
||||||
│ └── ui/ # Reusable UI components
|
|
||||||
│ └── ProjectCard.tsx
|
|
||||||
├── lib/
|
├── lib/
|
||||||
│ ├── prisma.ts # Prisma client singleton
|
│ ├── prisma.ts # Prisma client singleton
|
||||||
│ └── utils.ts # cn() utility
|
│ ├── s3.ts # Storage configuration (Cloudflare R2)
|
||||||
└── types/
|
│ └── visitor.ts # Analytics tracking logic
|
||||||
└── index.ts # TypeScript types
|
├── prisma/
|
||||||
prisma/
|
│ ├── schema.prisma # Database schema
|
||||||
├── schema.prisma # Database schema
|
│ └── migrations/ # Database migration history
|
||||||
└── seed.ts # Seed data
|
├── Dockerfile # Multi-stage production build
|
||||||
|
├── docker-compose.yml # Infrastructure orchestration
|
||||||
|
└── deploy.sh # Production deployment script
|
||||||
```
|
```
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### 1. Install dependencies
|
### 1. Install dependencies
|
||||||
```bash
|
```bash
|
||||||
npm install
|
pnpm install
|
||||||
# Also install react-icons for tech stack icons
|
|
||||||
npm install react-icons
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Setup environment
|
### 2. Setup environment
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Edit .env with your DATABASE_URL
|
# Edit .env with your DATABASE_URL and other credentials
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Setup database
|
### 3. Setup database
|
||||||
```bash
|
```bash
|
||||||
npm run db:generate # Generate Prisma client
|
pnpm db:generate # Generate Prisma client
|
||||||
npm run db:push # Push schema to database
|
npx prisma migrate dev # Run migrations
|
||||||
npx ts-node prisma/seed.ts # Seed with sample data
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Run development server
|
### 4. Run development server
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000)
|
Open [http://localhost:3000](http://localhost:3000)
|
||||||
|
|
||||||
## Customization
|
## Deployment (Production Ready)
|
||||||
|
|
||||||
### Personal Information
|
Project ini sudah dilengkapi dengan konfigurasi Docker untuk deployment mandiri (self-hosted).
|
||||||
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
|
|
||||||
|
|
||||||
### Database Content
|
### Deploy menggunakan Docker
|
||||||
After running seed, use Prisma Studio to manage content:
|
1. Pastikan Docker dan Docker Compose sudah terinstall di server.
|
||||||
|
2. Siapkan file `.env` di root direktori.
|
||||||
|
3. Jalankan script deployment:
|
||||||
```bash
|
```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:
|
## Architecture & Philosophy
|
||||||
- **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
|
|
||||||
|
|
||||||
## 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
|
Update profile Anda di:
|
||||||
2. Import to Vercel
|
- `src/app/layout.tsx` — SEO metadata (name, description)
|
||||||
3. Add `DATABASE_URL` environment variable
|
- `src/components/sections/...` — Konten section masing-masing
|
||||||
4. Deploy
|
|
||||||
|
|
||||||
For PostgreSQL, use [Supabase](https://supabase.com) or [Neon](https://neon.tech) for free hosted PostgreSQL.
|
|
||||||
|
|||||||
47
deploy.sh
47
deploy.sh
@@ -1,51 +1,30 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Exit immediately if a command exits with a non-zero status
|
# Berhenti jika ada error
|
||||||
set -e
|
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
|
if [ -f .env ]; then
|
||||||
echo "Loading environment variables from .env..."
|
echo "📄 Loading .env..."
|
||||||
export $(grep -v '^#' .env | xargs)
|
export $(grep -v '^#' .env | xargs)
|
||||||
fi
|
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
|
if docker compose version >/dev/null 2>&1; then
|
||||||
COMPOSE_CMD="docker compose"
|
COMPOSE_CMD="docker compose"
|
||||||
elif docker-compose version >/dev/null 2>&1; then
|
else
|
||||||
COMPOSE_CMD="docker-compose"
|
COMPOSE_CMD="docker-compose"
|
||||||
else
|
|
||||||
echo "Error: Docker Compose not found. Please install Docker Compose first."
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Configuration
|
echo "📦 Membangun dan menjalankan container aplikasi..."
|
||||||
DB_CONTAINER_NAME="fikri-portfolio-db"
|
# Langsung jalankan perintah docker-compose up
|
||||||
DB_PORT="5429"
|
# --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
|
# Membersihkan image sampah
|
||||||
EXISTING_DB=$(docker ps -q -f publish=${DB_PORT})
|
echo "🧹 Membersihkan image lama..."
|
||||||
|
|
||||||
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..."
|
|
||||||
docker image prune -f
|
docker image prune -f
|
||||||
|
|
||||||
echo "Deployment script finished successfully!"
|
echo "✨ Deployment Selesai! Aplikasi berjalan di port 4000."
|
||||||
|
|||||||
@@ -1,22 +1,4 @@
|
|||||||
services:
|
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:
|
app:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
@@ -27,7 +9,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "4000:4000"
|
- "4000:4000"
|
||||||
environment:
|
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}
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
R2_TOKEN: ${R2_TOKEN}
|
R2_TOKEN: ${R2_TOKEN}
|
||||||
R2_ACCESS_KEY: ${R2_ACCESS_KEY}
|
R2_ACCESS_KEY: ${R2_ACCESS_KEY}
|
||||||
@@ -35,13 +18,7 @@ services:
|
|||||||
R2_ENDPOINT: ${R2_ENDPOINT}
|
R2_ENDPOINT: ${R2_ENDPOINT}
|
||||||
R2_BUCKET_NAME: ${R2_BUCKET_NAME}
|
R2_BUCKET_NAME: ${R2_BUCKET_NAME}
|
||||||
IMAGE_URL: ${IMAGE_URL}
|
IMAGE_URL: ${IMAGE_URL}
|
||||||
depends_on:
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
command: >
|
command: >
|
||||||
sh -c "npx prisma migrate deploy && node server.js"
|
sh -c "npx prisma migrate deploy && node server.js"
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
|
|||||||
Reference in New Issue
Block a user