A modern, production-ready personal portfolio system built for AI engineers, researchers, and software developers. Features a powerful admin panel, markdown blog, publication management, and real-time Discord notifications.
- Dynamic Content Management - Projects, blog posts, publications, work experience, education, and skills
- Markdown Support - Write blog posts and project descriptions in markdown with syntax highlighting
- Admin Dashboard - Secure admin panel with authentication and content management
- Production-Grade Security - Rate limiting, input validation, CORS, security headers, and XSS protection
- Theme System - Light, dark, and system theme modes with smooth transitions
- Discord Notifications - Real-time contact form notifications via Discord webhooks
- Background Animations - Page-specific canvas animations (network graphs, matrix effects, code rain)
- Responsive Design - Mobile-first design with Tailwind CSS
- SEO Optimized - Dynamic sitemap, robots.txt, llms.txt, meta tags, Open Graph, and semantic HTML
- Type-Safe - Full TypeScript coverage with Prisma-generated types
- Zero-Cost Hosting - Deploy for free on Vercel
| Category | Technology |
|---|---|
| Framework | Next.js 15 (App Router) |
| Language | TypeScript 5.0 |
| Styling | Tailwind CSS |
| Database | PostgreSQL 15+ |
| ORM | Prisma 5 |
| Auth | Supabase Auth |
| Storage | Supabase Storage |
| Deployment | Vercel |
| Validation | Zod |
This portfolio includes production-grade security measures to protect against common web vulnerabilities:
-
Rate Limiting
- Contact form: 5 submissions per hour per IP
- Profile updates: 20 updates per hour
- Automatic IP detection (supports Cloudflare, reverse proxies)
- Standard headers:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset
-
Input Validation (Zod)
- Type-safe validation for all API endpoints
- Contact form: Name (2-100 chars), valid email, message (10-5000 chars)
- Profile data: Email format, URL validation, field length limits
- Detailed validation error messages
-
URL Sanitization
- Prevents XSS attacks via malicious URLs
- Only allows
http://andhttps://protocols - Validates all social links and external URLs
-
Security Headers
X-Content-Type-Options: nosniff- Prevents MIME sniffingX-Frame-Options: DENY- Prevents clickjackingX-XSS-Protection: 1; mode=block- XSS protectionReferrer-Policy: strict-origin-when-cross-originPermissions-Policy- Restricts browser features
-
CORS Configuration
- Configurable allowed origins
- Preflight request handling (OPTIONS)
- Proper credential handling
-
Authentication & Authorization
- Supabase JWT tokens
- Protected admin routes via middleware
- Session management
- Single admin account enforcement
-
Error Handling
- Sanitized error messages in production
- Detailed errors in development
- Prevents information leakage
- Standardized error format with timestamps
- Rate limiting: 5 submissions/hour per IP
- Validates name, email, message
- Discord webhook integration
- Returns: 201 Created or 429 Too Many Requests
- GET: Returns profile data
- PUT: Requires authentication
- Rate limiting: 20 updates/hour
- Validates all profile fields
- URL sanitization for links
✅ SQL Injection - Prisma ORM with parameterized queries ✅ XSS Attacks - URL sanitization + security headers ✅ CSRF - CORS configuration + SameSite cookies ✅ DoS/Spam - Rate limiting on all endpoints ✅ Injection Attacks - Zod validation with strict schemas ✅ Malicious URLs - Protocol validation (http/https only) ✅ Information Leakage - Error sanitization in production ✅ Invalid Data - Type-safe validation with detailed errors
src/lib/validations.ts- Zod schemas for input validationsrc/lib/rate-limit.ts- In-memory rate limitersrc/lib/api-utils.ts- Security headers, CORS, error handlingsrc/proxy.ts- Authentication middleware
HTTP 429 Too Many Requests
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2025-11-23T13:00:00.000Z
Retry-After: 3600
{
"error": "Rate limit exceeded. Please try again in 60 minutes.",
"timestamp": "2025-11-23T12:00:00.000Z"
}HTTP 400 Bad Request
{
"error": "email: Invalid email address",
"timestamp": "2025-11-23T12:00:00.000Z"
}- Node.js 20+
- PostgreSQL (or Supabase account)
- Discord server (optional, for notifications)
-
Clone the repository
git clone <your-repo-url> cd portfolio
-
Install dependencies
cd frontend npm install -
Set up environment variables
cp .env.local.example .env.local
Edit
.env.localwith your credentials (see Environment Variables section below for details) -
Initialize the database
npm run db:push npm run db:generate
-
Seed sample data (Optional but recommended)
Populate the database with sample content to see how the portfolio looks:
npm run db:seed
This creates:
- Sample profile with contact info
- 3 project showcases
- 2 blog posts
- 2 research publications
- Work experience and education
- Skills with proficiency levels
Note: You can customize or delete this sample data later through the admin panel.
-
Start development server
npm run dev
-
Create your admin account
- Navigate to
http://localhost:3000/admin/signup - Create your account (only one admin allowed)
- Verify email via Supabase
- Login at
/admin/login
- Navigate to
-
Explore your portfolio
- View sample content at
http://localhost:3000 - Edit/delete sample data via admin panel
- Replace with your actual content
- View sample content at
All environment variables needed to run this portfolio system:
# Database Connection
DATABASE_URL="postgresql://user:password@host:5432/database"
# For local development: postgresql://postgres:postgres@localhost:5432/portfolio
# For production (Supabase): Use connection pooling URL for serverless environments
# Format: postgresql://postgres.PROJECT_REF:[email protected]:6543/postgres?pgbouncer=true&connection_limit=1
# Supabase Authentication (Required)
NEXT_PUBLIC_SUPABASE_URL="https://your-project.supabase.co"
# Get from: Supabase Dashboard → Settings → API → Project URL
NEXT_PUBLIC_SUPABASE_ANON_KEY="your-anon-key-here"
# Get from: Supabase Dashboard → Settings → API → Project API keys → anon public
SUPABASE_SERVICE_ROLE_KEY="your-service-role-key-here"
# Get from: Supabase Dashboard → Settings → API → Project API keys → service_role (Keep secret!)
# Supabase Storage (Required for file uploads)
SUPABASE_STORAGE_BUCKET="documents_bucket"
# Create bucket in: Supabase Dashboard → Storage → New bucket# Discord Webhook Notifications (Optional but recommended)
DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN"
# Get from: Discord Server → Settings → Integrations → Webhooks → New Webhook
# Sends real-time notifications when users submit the contact form
# Site URL (Required for SEO)
NEXT_PUBLIC_SITE_URL="https://yourdomain.com"
# For local dev: http://localhost:3000
# For production: Your actual domain (used in sitemap.xml and robots.txt)If using Appwrite instead of Supabase for authentication and storage:
# Appwrite Configuration
NEXT_PUBLIC_APPWRITE_ENDPOINT="https://cloud.appwrite.io/v1"
# For Appwrite Cloud: https://cloud.appwrite.io/v1
# For Self-hosted: https://your-appwrite-domain.com/v1
NEXT_PUBLIC_APPWRITE_PROJECT_ID="your-project-id"
# Get from: Appwrite Console → Project Settings → Project ID
APPWRITE_API_KEY="your-api-key"
# Get from: Appwrite Console → Project Settings → API Keys → Create API Key
# Scopes needed: sessions.write, users.read, storage.write
# Database (Still use PostgreSQL with Appwrite)
DATABASE_URL="postgresql://user:password@host:5432/database"
# Appwrite's database uses NoSQL collections - keep PostgreSQL for relational data
# Optional
DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..."
NEXT_PUBLIC_SITE_URL="https://yourdomain.com"Note: When using Appwrite, you'll need to modify the authentication logic in src/lib/auth-client.ts, src/proxy.ts, and storage logic. See the Appwrite Integration section for detailed migration steps.
| Variable | Purpose | Visibility | Required |
|---|---|---|---|
DATABASE_URL |
PostgreSQL connection string | Server-only | ✅ Yes |
NEXT_PUBLIC_SUPABASE_URL |
Supabase project URL | Client & Server | ✅ Yes |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase public API key | Client & Server | ✅ Yes |
SUPABASE_SERVICE_ROLE_KEY |
Supabase admin key (bypass RLS) | Server-only | ✅ Yes |
SUPABASE_STORAGE_BUCKET |
File storage bucket name | Server-only | ✅ Yes |
DISCORD_WEBHOOK_URL |
Discord notifications endpoint | Server-only | |
NEXT_PUBLIC_SITE_URL |
Your domain for SEO | Client & Server |
Create .env.local in the frontend/ directory:
cd frontend
cp .env.local.example .env.local
# Edit .env.local with your valuesAdd environment variables through your platform's dashboard:
- Vercel: Settings → Environment Variables
- Railway: Variables tab
- Render: Environment → Environment Variables
- AWS/Docker: Use secrets management or
.envfile
-
Create a Supabase project: https://supabase.com/dashboard
-
Get your credentials:
- Go to Settings → API
- Copy Project URL →
NEXT_PUBLIC_SUPABASE_URL - Copy anon public key →
NEXT_PUBLIC_SUPABASE_ANON_KEY - Copy service_role key →
SUPABASE_SERVICE_ROLE_KEY
-
Set up Database:
- Supabase automatically provides PostgreSQL
- Get connection string from Settings → Database
- For serverless (Vercel/Railway): Use "Connection Pooling" URL (port 6543)
- For local/VPS: Use "Direct Connection" URL (port 5432)
-
Create Storage Bucket:
- Go to Storage → New Bucket
- Name:
documents_bucket - Set to Public or Private based on your needs
- Copy bucket name →
SUPABASE_STORAGE_BUCKET
.env.env.local.env.production
✅ Safe to commit:
.env.example(template without actual values).env.local.example(template without actual values)
🔒 Keep secret (server-only):
SUPABASE_SERVICE_ROLE_KEY- Bypasses Row Level SecurityDATABASE_URL- Contains database passwordDISCORD_WEBHOOK_URL- Can be used to spam your Discord
✅ Safe to expose (public):
NEXT_PUBLIC_SUPABASE_URL- Public project URLNEXT_PUBLIC_SUPABASE_ANON_KEY- Limited by RLS policiesNEXT_PUBLIC_SITE_URL- Your public domain
frontend/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (pages)/ # Public pages
│ │ │ ├── page.tsx # Homepage
│ │ │ ├── projects/ # Projects showcase
│ │ │ ├── blog/ # Blog posts
│ │ │ └── publications/ # Research papers
│ │ ├── admin/ # Admin dashboard
│ │ │ ├── profile/ # Profile settings
│ │ │ ├── projects/ # Manage projects
│ │ │ ├── blog/ # Manage blog posts
│ │ │ ├── publications/ # Manage publications
│ │ │ └── contacts/ # View submissions
│ │ ├── api/ # API routes
│ │ │ ├── contact/ # Contact form + Discord
│ │ │ └── profile/ # Profile data
│ │ └── actions/ # Server actions
│ ├── components/ # React components
│ │ ├── animations/ # Canvas animations
│ │ ├── Button.tsx # UI components
│ │ └── ...
│ ├── lib/ # Utilities
│ │ ├── prisma.ts # Prisma client
│ │ ├── auth-client.ts # Auth helpers
│ │ ├── validations.ts # Zod validation schemas
│ │ ├── rate-limit.ts # Rate limiting middleware
│ │ └── api-utils.ts # Security & API utilities
│ └── proxy.ts # Auth middleware
├── prisma/
│ └── schema.prisma # Database schema
├── public/ # Static assets
└── package.json
- Navigate to
/admin/profile - Configure:
- Personal information (name, title, bio)
- Contact details (email, phone, location)
- Social links (GitHub, LinkedIn, Twitter)
- Profile image and resume URLs
- Hero section text
- Availability status
- Go to
/admin/projects→ New Project - Fill in project details:
- Title and description
- Markdown content with code examples
- Tech stack (comma-separated)
- Links (GitHub, demo, docs)
- Display order
- Toggle Published to make it visible
Note: First 3 published projects appear on homepage
- Go to
/admin/blog→ New Post - Write in markdown:
- Supports code blocks with syntax highlighting
- Images via markdown syntax
- Custom components (if configured)
- Set excerpt, tags, and reading time
- Choose publication date
- Toggle Published when ready
Note: 3 most recent posts appear on homepage
- Go to
/admin/publications - Add research papers with:
- Title, authors, venue, year
- Abstract
- PDF URL, DOI, arXiv ID
- Tags for categorization
Note: Cards link to external resources (PDF/DOI/arXiv)
Get instant Discord notifications when someone contacts you:
-
Create Discord Webhook:
- Server Settings → Integrations → Webhooks
- Create new webhook
- Copy webhook URL
-
Add to environment:
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
-
Restart server to apply changes
You'll receive formatted Discord messages with:
- Sender name and email
- Message content
- Direct link to admin panel
- Timestamp and contact ID
Navigate to /admin/contacts to:
- View all messages
- Filter by read/unread status
- Mark as read/unread
- Delete old submissions
- Click email to reply directly
Change default theme in src/app/layout.tsx:
<ThemeProvider
attribute="class"
defaultTheme="light" // Options: "light", "dark", "system"
enableSystem
>Page-specific animations in src/components/animations/:
- MatrixAnimation - Publications page (floating matrices)
- CodeAnimation - Blog pages (code snippets + binary rain)
- NetworkAnimation - Projects page (network graph)
- HeroBackground - Homepage (gradient effects)
Customize by editing animation files or remove component from page.
Modify tailwind.config.ts to change:
- Primary/secondary colors
- Dark mode variants
- Typography styles
- Spacing/sizing
-
Update database schema:
# Edit prisma/schema.prisma npm run db:push npm run db:generate -
Create admin UI: Add page in
src/app/admin/ -
Create public view: Add page in
src/app/ -
Add server actions: Create mutations in
src/app/actions/
The portfolio includes comprehensive SEO features out of the box:
Automatically generated sitemap that includes:
- All published blog posts
- All published projects
- Main section pages (blog, projects, publications)
- Proper priority and change frequency settings
- Last modified dates for each page
The sitemap is generated from src/app/sitemap.ts and automatically updates when you publish new content.
Controls search engine and AI crawler access:
- Allows: Public pages (blog, projects, publications)
- Blocks: Admin dashboard, API endpoints, auth routes
- Supports: Google, Bing, ChatGPT, Claude, Perplexity, etc.
- Features: Crawl delays to protect server resources
Located at: public/robots.txt
Helps AI systems understand your portfolio:
- Your professional information and expertise
- Site structure and content descriptions
- Usage guidelines for AI training
- Attribution requirements
- Technical stack information
Located at: public/llms.txt
Important: Update both files with your actual domain and information:
- Edit
public/robots.txt- Replaceyourdomain.comwith your actual domain - Edit
public/llms.txt- Update personal information and URLs - Set
NEXT_PUBLIC_SITE_URLin.envfor the sitemap
- Meta Tags: Each page has proper title, description, and Open Graph tags
- Semantic HTML: Proper heading hierarchy and ARIA labels
- Image Optimization: Next.js Image component with lazy loading
- Performance: Server-side rendering, static generation where possible
- Mobile-First: Responsive design for better mobile rankings
- Structured Data: Consider adding JSON-LD for rich snippets
After deployment:
- Check sitemap:
https://yourdomain.com/sitemap.xml - Check robots.txt:
https://yourdomain.com/robots.txt - Check llms.txt:
https://yourdomain.com/llms.txt - Submit sitemap to Google Search Console
- Verify with Bing Webmaster Tools
- Test with: Google Rich Results Test
-
Import project to Vercel
- Connect your GitHub repository
- Set root directory to
frontend - Framework preset: Next.js
-
Configure environment variables
Add all variables from
.env.local:DATABASE_URL NEXT_PUBLIC_SUPABASE_URL NEXT_PUBLIC_SUPABASE_ANON_KEY SUPABASE_SERVICE_ROLE_KEY DISCORD_WEBHOOK_URL (optional) NEXT_PUBLIC_SITE_URL -
Set up database
- Use Supabase PostgreSQL (recommended)
- After first deploy, run migrations:
npm run db:push
-
Configure Supabase Storage
- Create bucket:
portfolio-files - Set appropriate access policies
- Create bucket:
-
Deploy
- Push to
mainbranch - Vercel auto-deploys on commits
- Push to
- Add domain in Vercel dashboard
- Update DNS records with your registrar
- SSL certificate auto-provisioned
- Update
NEXT_PUBLIC_SITE_URLenvironment variable
While Vercel is recommended for its seamless Next.js integration, this portfolio can be deployed to other platforms:
Railway provides excellent PostgreSQL and Next.js support with automatic deployments:
-
Create Railway Project
- Connect your GitHub repository
- Railway auto-detects Next.js
-
Add PostgreSQL Database
- Click "New" → "Database" → "PostgreSQL"
- Railway provides
DATABASE_URLautomatically
-
Configure Environment Variables
# Railway provides DATABASE_URL automatically NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key SUPABASE_SERVICE_ROLE_KEY=your-service-role-key SUPABASE_STORAGE_BUCKET=documents_bucket DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... NEXT_PUBLIC_SITE_URL=https://your-app.railway.app -
Set Root Directory
- Settings → Set
Root Directorytofrontend
- Settings → Set
-
Deploy
- Railway auto-builds and deploys on every commit
- Get your app URL from the deployment
- Run migrations: Railway CLI or connect via Prisma Studio
Note: Railway requires connection pooling for PostgreSQL. If using external database (Supabase), use the pooling URL with port 6543.
-
Create New Web Service
- Connect repository
- Root Directory:
frontend - Build Command:
npm install && npm run build - Start Command:
npm start
-
Add PostgreSQL Database (optional)
- Create PostgreSQL database in Render
- Or use external database (Supabase)
-
Set Environment Variables (same as above)
-
Deploy - Render auto-deploys from your branch
For VPS, AWS EC2, DigitalOcean, etc.:
-
Create
Dockerfileinfrontend/directory:FROM node:20-alpine AS base # Install dependencies only when needed FROM base AS deps WORKDIR /app COPY package*.json ./ RUN npm ci # Rebuild the source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Generate Prisma Client RUN npx prisma generate # Build Next.js ENV NEXT_TELEMETRY_DISABLED 1 RUN npm run build # Production image FROM base AS runner WORKDIR /app ENV NODE_ENV production ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT 3000 ENV HOSTNAME "0.0.0.0" CMD ["node", "server.js"]
-
Update
next.config.js:/** @type {import('next').NextConfig} */ const nextConfig = { output: 'standalone', // ... rest of config } module.exports = nextConfig
-
Build and Run:
# Build image docker build -t portfolio-app . # Run container docker run -p 3000:3000 \ -e DATABASE_URL="postgresql://..." \ -e NEXT_PUBLIC_SUPABASE_URL="https://..." \ -e NEXT_PUBLIC_SUPABASE_ANON_KEY="..." \ -e SUPABASE_SERVICE_ROLE_KEY="..." \ -e SUPABASE_STORAGE_BUCKET="documents_bucket" \ -e NEXT_PUBLIC_SITE_URL="https://yourdomain.com" \ portfolio-app
-
Or use Docker Compose:
# docker-compose.yml version: '3.8' services: app: build: context: ./frontend dockerfile: Dockerfile ports: - "3000:3000" environment: - DATABASE_URL=${DATABASE_URL} - NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL} - NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY} - SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY} - SUPABASE_STORAGE_BUCKET=${SUPABASE_STORAGE_BUCKET} - NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL} depends_on: - db db: image: postgres:15-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=portfolio volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" volumes: postgres_data:
Run with:
docker-compose up -d
Note: This portfolio currently uses Supabase for authentication and storage. If you want to use Appwrite instead, you'll need to modify the authentication and storage logic. Here's how:
Appwrite Cloud provides authentication and storage but doesn't host Next.js apps. You can:
- Use Appwrite for auth/storage
- Host Next.js on Vercel/Railway/Render
- Keep PostgreSQL for your database (Appwrite's database uses collections, not relational tables)
Required Changes:
-
Install Appwrite SDK:
npm install appwrite
-
Replace Supabase Client (
src/lib/appwrite.ts):import { Client, Account, Storage } from 'appwrite'; const client = new Client() .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!) // 'https://cloud.appwrite.io/v1' .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!); export const account = new Account(client); export const storage = new Storage(client);
-
Environment Variables:
# Appwrite Configuration NEXT_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 NEXT_PUBLIC_APPWRITE_PROJECT_ID=your-project-id APPWRITE_API_KEY=your-api-key # Keep PostgreSQL for database DATABASE_URL=postgresql://user:password@host:5432/database # Optional DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... NEXT_PUBLIC_SITE_URL=https://yourdomain.com
-
Update Authentication (
src/lib/auth-client.tsandsrc/proxy.ts):- Replace Supabase auth calls with Appwrite's
account.createSession() - Update middleware to verify Appwrite sessions
- Modify login/signup pages to use Appwrite SDK
- Replace Supabase auth calls with Appwrite's
-
Update File Storage:
- Replace Supabase Storage with Appwrite Storage
- Update upload endpoints to use
storage.createFile()
Deployment with Appwrite:
- Frontend: Deploy to Vercel/Railway/Render (see deployment options above)
- Auth & Storage: Managed by Appwrite Cloud
- Database: Keep PostgreSQL on Supabase or Railway
For full control, self-host Appwrite on your own infrastructure:
-
Install Appwrite (requires Docker):
docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ appwrite/appwrite:1.5.7
-
Configure Appwrite:
- Access Appwrite Console at
http://localhost:80 - Create project and get credentials
- Set up authentication providers (Email/Password)
- Create storage buckets
- Configure OAuth providers if needed
- Access Appwrite Console at
-
Deploy Appwrite:
- VPS/Cloud: Run on AWS EC2, DigitalOcean Droplet, etc.
- Docker Compose: Manages all Appwrite services
- Requires: Docker, Docker Compose, 2GB+ RAM
-
Update Environment Variables:
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://your-appwrite-domain.com/v1 NEXT_PUBLIC_APPWRITE_PROJECT_ID=your-project-id APPWRITE_API_KEY=your-api-key
| Feature | Supabase (Current) | Appwrite Alternative |
|---|---|---|
| Auth | Built-in JWT | Email, OAuth, Magic Links |
| Storage | S3-compatible | Built-in file storage |
| Database | PostgreSQL (relational) | Collections (NoSQL) or keep PostgreSQL |
| Realtime | PostgreSQL subscriptions | WebSocket subscriptions |
| Functions | Edge Functions | Cloud Functions |
| Hosting | None (use Vercel) | None (use Vercel/Railway) |
| Free Tier | 500MB DB, 1GB storage | 75K users, 2GB storage |
| Open Source | Yes | Yes |
Low effort (Keep PostgreSQL, use Appwrite for auth only):
- ✅ 2-3 hours for auth migration
- ✅ Keep all database logic unchanged
- ✅ Keep Prisma ORM
⚠️ Need to update auth middleware and login pages
High effort (Full migration to Appwrite):
- ❌ Rewrite all database logic (Prisma → Appwrite SDK)
- ❌ Convert relational schema to collections
- ❌ Rewrite queries and relationships
- ❌ 2-3 days of development
⚠️ Not recommended for this project
Recommended: Use Appwrite for auth/storage + keep PostgreSQL for data.
For enterprise deployments:
-
AWS Elastic Beanstalk or ECS:
- Use Docker deployment (see above)
- Connect to RDS PostgreSQL
- Set environment variables in console
- Configure load balancer for HTTPS
-
Azure App Service:
- Deploy from GitHub
- Use Azure Database for PostgreSQL
- Configure app settings for env vars
-
Google Cloud Run:
- Build container image
- Push to Google Container Registry
- Deploy with Cloud SQL PostgreSQL
- Set environment variables
| Platform | Database Option | Connection Type | Notes |
|---|---|---|---|
| Vercel | Supabase/Neon/Planetscale | Connection Pooling (6543) | Serverless requires pooling |
| Railway | Railway PostgreSQL | Direct or Pooling | Both work, pooling recommended |
| Render | Render PostgreSQL | Direct Connection (5432) | Not serverless, direct works |
| Docker/VPS | Self-hosted or Supabase | Direct Connection (5432) | Long-running process, direct connection |
| AWS Lambda | RDS/Aurora | Connection Pooling | Serverless, use RDS Proxy |
Use Connection Pooling (port 6543) when:
- ✅ Deploying to serverless platforms (Vercel, AWS Lambda)
- ✅ Application creates many short-lived connections
- ✅ Need to limit database connections
Use Direct Connection (port 5432) when:
- ✅ Running on traditional servers (VPS, Docker)
- ✅ Long-running Node.js process
- ✅ Need better performance (pooling adds latency)
# Development
npm run dev # Start dev server
# Database
npm run db:push # Push schema changes (no migration)
npm run db:migrate # Create migration files
npm run db:generate # Generate Prisma client
npm run db:studio # Open Prisma Studio GUI
npm run db:seed # Populate database with sample data
# Build
npm run build # Production build
npm start # Start production server
# Code Quality
npm run type-check # TypeScript validation
npm run lint # ESLintDevelopment workflow (faster, no migration files):
# Make schema changes
npm run db:push
npm run db:generateProduction workflow (creates migration history):
# Make schema changes
npm run db:migrate
npm run db:generateThis portfolio uses modern Next.js patterns:
- Server Components: Direct database access, no API calls
- Server Actions: Form mutations without REST endpoints
- Client Components: Interactive UI (theme toggle, forms)
- API Routes: External access only (contact form, webhooks)
Example Server Component:
// app/projects/page.tsx
import { prisma } from '@/lib/prisma'
export default async function ProjectsPage() {
const projects = await prisma.project.findMany({
where: { published: true }
})
return <ProjectGrid projects={projects} />
}- SEO-friendly slugs: Keep short, descriptive
- Image optimization: Compress before upload
- Good excerpts: Shown in cards and search results
- Consistent tags: Use standard naming conventions
- Display order: Control featured content on homepage
- Uses Incremental Static Regeneration (ISR)
- Image optimization with Next.js Image
- Edge runtime for auth middleware
- Automatic code splitting
See the Security & API Protection section above for comprehensive security documentation.
Key security practices:
- Rate limiting on all public endpoints (prevents DoS/spam)
- Input validation with Zod schemas
- URL sanitization (prevents XSS)
- Security headers on all responses
- Single admin account enforcement
- Environment variables never committed
- Service role key kept server-side only
- Authentication on all admin routes
- Error sanitization in production
- Database: Supabase provides automatic backups
- Content: Export via Prisma Studio regularly
- Files: Download from Supabase Storage
- Config: Keep
.env.local.exampleupdated
rm -rf .next
npm run devnpm run db:push
npm run db:generate- Verify Supabase credentials in
.env.local - Check email confirmation in Supabase dashboard
- Clear browser cookies
- Review
src/proxy.tsmiddleware configuration
npm run type-check # Check TypeScript errors
# Review Vercel build logs for specific errors- Verify
DISCORD_WEBHOOK_URLis set correctly - Check webhook channel permissions
- Test webhook manually with curl
- Review server logs for error messages
| Service | Tier | Monthly Cost |
|---|---|---|
| Vercel | Hobby | $0 |
| Supabase | Free/Pro | $0-25 |
| Domain | Various | $1-2 |
| Total | $1-27/mo |
Free tier includes:
- Unlimited deployments
- SSL certificates
- CDN
- 500MB Postgres storage
- 1GB file storage
- Email auth
- Documentation: See detailed guides above
- Issues: Open GitHub issue for bugs
- Architecture: See
ARCHITECTURE.mdfor system design
MIT License - feel free to use for your own portfolio
Built with ❤️ using Next.js, TypeScript, and Prisma