A modern photography portfolio website built with Next.js 16, featuring interactive maps, photo management, and a comprehensive dashboard.
- Next.js 16 with React 19 and React Compiler
- TanStack Query v5 for advanced data fetching and caching
- tRPC v11 for end-to-end type-safe APIs
- Interactive Maps with Mapbox GL JS integration
- Photo Management with EXIF data extraction and iPhone album integration
- Real-time Dashboard with analytics and statistics
- Modern UI built with Tailwind CSS and shadcn/ui components
- Authentication powered by Better Auth
- Database using Drizzle ORM with PostgreSQL
- File Storage via S3-compatible storage
Before deploying, ensure you have:
- Node.js 18+ or Bun runtime
- PostgreSQL database (recommended: Neon, Supabase, or Vercel Postgres)
- S3-compatible storage for image storage (AWS S3, Cloudflare R2, DigitalOcean Spaces, etc.)
- Mapbox account for map features
- Vercel account for deployment (or any Node.js hosting provider)
# Clone the repository
git clone https://github.com/ECarry/photography-website.git
cd photography-website
# Install dependencies
bun install
# or
npm installCreate a .env file in the root directory:
cp .env.example .envConfigure the following environment variables:
# PostgreSQL connection string
DATABASE_URL=postgresql://username:password@host:port/database_name?sslmode=requireFor Neon Database:
- Create account at neon.tech
- Create a new project
- Copy the connection string from dashboard
For Supabase:
- Create project at supabase.com
- Go to Settings > Database
- Copy the connection string
# Generate a random secret key (32+ characters)
BETTER_AUTH_SECRET=your-super-secret-key-here
# Your app's base URL
BETTER_AUTH_URL=https://your-domain.com
NEXT_PUBLIC_APP_URL=https://your-domain.com# S3-compatible storage settings
S3_ENDPOINT=https://your-s3-endpoint.com
S3_BUCKET_NAME=your-bucket-name
S3_PUBLIC_URL=https://your-custom-domain.com
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key
NEXT_PUBLIC_S3_PUBLIC_URL=https://your-custom-domain.comSupported Storage Providers:
Cloudflare R2:
S3_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
S3_BUCKET_NAME=your-bucket-name
NEXT_PUBLIC_S3_PUBLIC_URL=https://your-custom-domain.comAWS S3:
S3_ENDPOINT=https://s3.amazonaws.com
S3_BUCKET_NAME=your-aws-bucket
NEXT_PUBLIC_S3_PUBLIC_URL=https://your-bucket.s3.amazonaws.comDigitalOcean Spaces:
S3_ENDPOINT=https://nyc3.digitaloceanspaces.com
S3_BUCKET_NAME=your-space-name
NEXT_PUBLIC_S3_PUBLIC_URL=https://your-space.nyc3.digitaloceanspaces.comMinIO (Self-hosted):
S3_ENDPOINT=http://localhost:9000
S3_BUCKET_NAME=your-minio-bucket
NEXT_PUBLIC_S3_PUBLIC_URL=http://localhost:9000/your-bucketWasabi:
S3_ENDPOINT=https://s3.wasabisys.com
S3_BUCKET_NAME=your-wasabi-bucket
NEXT_PUBLIC_S3_PUBLIC_URL=https://your-bucket.s3.wasabisys.comIf you are not using Cloudflare R2 (or you donβt want to use the Cloudflare-specific custom image loader), make sure to remove or update the following in next.config.*:
loader: 'custom',
loaderFile: './src/lib/cloudflare-image-loader.ts',Otherwise, Next.js image optimization/loading may fail locally or in production.
Setup Instructions:
- Choose your preferred S3-compatible storage provider
- Create an account and set up a bucket
- Generate API credentials (Access Key ID and Secret Access Key)
- Configure the endpoint URL for your provider
- (Optional) Setup custom domain for public access
# Mapbox access token
NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN=pk.your-mapbox-tokenGet Mapbox Token:
- Create account at mapbox.com
- Go to Account > Access Tokens
- Create a new token with appropriate scopes
# Default admin user for seeding
SEED_USER_EMAIL=admin@yourdomain.com
SEED_USER_PASSWORD=your-secure-password
SEED_USER_NAME=Admin User# Push database schema
bun run db:push
# Create admin user
bun run seed:user# Build the application
bun run build
# Test the production build locally
bun run startVisit http://localhost:3000 to verify everything works correctly.
# Install Vercel CLI
npm i -g vercel
# Login to Vercel
vercel login
# Deploy
vercel --prod- Push your code to GitHub/GitLab/Bitbucket
- Connect your repository to Vercel
- Configure environment variables in Vercel dashboard
- Deploy automatically on push
In your Vercel dashboard, add all environment variables from your .env file:
- Go to Project Settings > Environment Variables
- Add each variable with appropriate values for production
- Make sure to update URLs to use your production domain
# If you need to run migrations on production
vercel env pull .env.local
bun run db:push# Seed admin user in production
vercel env pull .env.local
bun run seed:user-
Change Summary
- The database
urlfield now stores the S3 object key (e.g.,photos/IMG_0001.jpg) instead of a full public URL. - Benefit: You can switch domains or CDNs freely by updating environment variables for the public base URL, without mass-updating the database.
- The database
-
Migration Steps (run before production, recommended)
- Backup your database (strongly recommended).
- Run the cleanup script to convert existing full URLs to S3 keys:
bun run clean:photo-urls
- Verify the result: spot-check several records;
urlshould look like a key such aspath/to/object.jpg. - If you need to rollback, run:
bun run rollback:photo-urls
-
Notes
- Ensure your public access domain is configured via
S3_PUBLIC_URLorNEXT_PUBLIC_S3_PUBLIC_URL. At runtime, the app combines this base URL with the key to form a full public URL. - If you have custom prefixes or multiple buckets, validate the script behavior in a staging environment first.
- Ensure your public access domain is configured via
This project supports two Docker deployment modes: Standalone (self-hosted) and Cloud (managed services).
Run the entire stack (App, PostgreSQL, RustFS) locally. Ideal for testing and self-hosting.
docker compose up -d
# or explicitly:
docker compose -f docker-compose.standalone.yml up -d- App: http://localhost:3000
- Postgres: localhost:5432
- RustFS (S3): localhost:9000
- RustFS Console: http://localhost:9001
Run only the App container, connecting to external services (e.g., Neon Postgres, AWS S3, Cloudflare R2).
- Create a
.envfile with your credentials:DATABASE_PROVIDER=cloud DATABASE_URL="postgres://..." BETTER_AUTH_SECRET="..." S3_ACCESS_KEY_ID="..." S3_SECRET_ACCESS_KEY="..." S3_BUCKET_NAME="..." S3_ENDPOINT="..." # Optional S3_PUBLIC_URL="..."
- Start the service:
docker compose -f docker-compose.cloud.yml up -d
This project uses a Runtime Build strategy for Docker. The Next.js application is built inside the container when it starts, not when the Docker image is built. This ensures that Static Site Generation (SSG) can successfully fetch data from the database (which is only available at runtime in the Standalone mode).
- First Start: Will take 1-2 minutes to compile.
- Restarts: Will be fast (cached via
next_cachevolume).
-
Vercel Custom Domain:
- Go to Project Settings > Domains
- Add your custom domain
- Configure DNS records as instructed
-
Update Environment Variables:
BETTER_AUTH_URL=https://your-custom-domain.com NEXT_PUBLIC_APP_URL=https://your-custom-domain.com
-
Enable Vercel Analytics:
npm install @vercel/analytics
-
Configure Image Optimization:
- Ensure S3-compatible storage is properly configured
- Set up custom domain for your storage bucket
- Configure CDN settings (CloudFlare, AWS CloudFront, etc.)
-
Environment Variables:
- Never commit
.envfiles - Use strong, unique secrets
- Rotate keys regularly
- Never commit
-
Database Security:
- Use connection pooling
- Enable SSL connections
- Restrict database access by IP
-
File Upload Security:
- Configure proper CORS settings
- Implement file type validation
- Set upload size limits
The application is fully responsive and optimized for mobile devices:
- Progressive Web App features
- Touch-friendly interface
- Optimized images with lazy loading
- Fast loading with Next.js optimizations
# Clear Next.js cache
rm -rf .next
# Reinstall dependencies
rm -rf node_modules
bun install- Verify
DATABASE_URLis correct - Check database server status
- Ensure SSL settings match requirements
- Verify S3 storage credentials
- Check CORS settings on your storage bucket
- Ensure bucket permissions are correct
- Verify endpoint URL is correct for your provider
- Verify Mapbox token is valid
- Check token permissions and scopes
- Ensure domain is authorized in Mapbox settings
- Next.js Documentation
- Vercel Deployment Guide
- Drizzle ORM Documentation
- Better Auth Documentation
- AWS S3 Documentation
- Cloudflare R2 Documentation
- DigitalOcean Spaces Documentation
- Mapbox Documentation
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
If you find this project helpful, please give it a βοΈ on GitHub!
This project is licensed under the MIT License - see the LICENSE file for details.
Need help? Check the troubleshooting section above or open an issue in the repository.

