Homepage was rendering 71MB of HTML because every uploaded image (gallery, products, proofs, custom requests, design previews, invoices, receipts) was stored as base64 in Postgres and double-embedded via RSC hydration payloads. Adds src/lib/storage.ts (sharp-based compression, disk storage under public/uploads/) and a new /api/design-uploads endpoint for images added inside the design tool, migrates existing rows, and drops the now-unused base64 columns and the dead ProductImage table. Also fixes a systemic bug from the Next.js 16 upgrade where dynamic pages across the app still destructured params/searchParams synchronously instead of awaiting them as Promises, which was silently breaking filters/params and crashing /products/[slug] outright. Updates README.md and SETUP_AND_BUILD.md to reflect Postgres + Next.js 16 and document the new image storage architecture and backup requirements. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
568 lines
16 KiB
Markdown
568 lines
16 KiB
Markdown
# Complete Setup & Build Guide
|
|
|
|
This document covers everything needed to develop, build, and deploy Craft2Prints.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Local Development Setup](#local-development-setup)
|
|
2. [Configuration Reference](#configuration-reference)
|
|
3. [Database Management](#database-management)
|
|
4. [Image & File Storage](#image--file-storage)
|
|
5. [Feature Setup](#feature-setup)
|
|
6. [Building for Production](#building-for-production)
|
|
7. [Deployment](#deployment)
|
|
8. [Troubleshooting](#troubleshooting)
|
|
|
|
---
|
|
|
|
## Local Development Setup
|
|
|
|
### Prerequisites
|
|
|
|
**Node.js 18 or later** is required:
|
|
```bash
|
|
node -v # Check your version
|
|
```
|
|
|
|
If not installed, download from https://nodejs.org (LTS version recommended).
|
|
|
|
**PostgreSQL** — the app connects to a Postgres database (`prisma/schema.prisma`'s `datasource` is fixed to `postgresql`). Install it locally (https://www.postgresql.org/download/) or point `DATABASE_URL` at any reachable Postgres instance (a Docker container, a managed database, another machine on your network).
|
|
|
|
### Step 1: Clone and Install
|
|
|
|
```bash
|
|
# Clone the repository
|
|
git clone <your-repo-url>
|
|
cd craft2prints
|
|
|
|
# Install dependencies
|
|
npm install
|
|
# This automatically runs: npx prisma generate
|
|
```
|
|
|
|
### Step 2: Environment Setup
|
|
|
|
```bash
|
|
# Copy example environment file
|
|
cp .env.example .env
|
|
```
|
|
|
|
Edit `.env` and configure at minimum:
|
|
```env
|
|
DATABASE_URL="postgresql://user:password@localhost:5432/craft2prints?schema=public"
|
|
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
|
|
ADMIN_USER="admin"
|
|
ADMIN_PASSWORD="changeme"
|
|
ADMIN_SESSION_SECRET="<generate-random-string>"
|
|
SESSION_SECRET="<generate-random-string>"
|
|
```
|
|
|
|
For random secrets, use:
|
|
```bash
|
|
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
```
|
|
|
|
### Step 3: Database Setup
|
|
|
|
```bash
|
|
# Create the database (make sure the DB named in DATABASE_URL already exists — e.g. `createdb craft2prints`)
|
|
# then sync the schema to it
|
|
npm run db:push
|
|
|
|
# Load starter products
|
|
npm run db:seed
|
|
```
|
|
|
|
### Step 4: Start Development Server
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
Open http://localhost:3000 in your browser.
|
|
|
|
**Auto-reload on file changes:** The dev server watches your code and reloads automatically. If you change `prisma/schema.prisma`, run `npm run db:push` manually.
|
|
|
|
---
|
|
|
|
## Configuration Reference
|
|
|
|
### Essential Variables
|
|
|
|
| Variable | Description | Example |
|
|
|----------|-------------|---------|
|
|
| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@host:5432/craft2prints?schema=public` |
|
|
| `ADMIN_USER` | Admin login username | `admin` |
|
|
| `ADMIN_PASSWORD` | Admin login password | `changeme` |
|
|
| `ADMIN_SESSION_SECRET` | Session signing key (random) | 32-byte hex string |
|
|
| `SESSION_SECRET` | Customer session key (random) | 32-byte hex string |
|
|
|
|
### Stripe Configuration
|
|
|
|
Get from https://dashboard.stripe.com (make sure you're in **test mode** first):
|
|
|
|
```env
|
|
STRIPE_SECRET_KEY="sk_test_..." # From Developers > API keys
|
|
STRIPE_WEBHOOK_SECRET="whsec_..." # From Stripe CLI (see below)
|
|
```
|
|
|
|
**Local Webhook Setup:**
|
|
```bash
|
|
# Install Stripe CLI: https://docs.stripe.com/stripe-cli
|
|
stripe login
|
|
stripe listen --forward-to localhost:3000/api/webhook
|
|
# Copy the whsec_... secret into .env
|
|
```
|
|
|
|
### Email (SMTP)
|
|
|
|
For order confirmations and password resets:
|
|
|
|
```env
|
|
SMTP_HOST="smtp.gmail.com"
|
|
SMTP_PORT="587"
|
|
SMTP_USER="your-email@gmail.com"
|
|
SMTP_PASSWORD="your-app-password" # Not your normal password!
|
|
MAIL_FROM="Craft2Prints <your-email@gmail.com>"
|
|
ADMIN_NOTIFY_EMAIL="admin@example.com" # For photo request alerts
|
|
```
|
|
|
|
**For Gmail:** Generate an app password at https://myaccount.google.com/apppasswords (requires 2FA enabled).
|
|
|
|
### CAPTCHA (Cloudflare Turnstile)
|
|
|
|
Protects public forms (registration, contact, custom requests):
|
|
|
|
```env
|
|
NEXT_PUBLIC_TURNSTILE_SITE_KEY="..." # Get from dash.cloudflare.com
|
|
TURNSTILE_SECRET_KEY="..."
|
|
```
|
|
|
|
If not configured, forms work without CAPTCHA. Optional, not required.
|
|
|
|
### Optional Variables
|
|
|
|
```env
|
|
NEXT_PUBLIC_FACEBOOK_APP_ID="..." # For Messenger sharing
|
|
NEXT_PUBLIC_SITE_URL="http://localhost:3000" # For design approval links
|
|
```
|
|
|
|
---
|
|
|
|
## Database Management
|
|
|
|
The app uses `prisma db push` (schema-driven sync), not versioned `prisma migrate` files — `prisma/schema.prisma` is the single source of truth for the database shape.
|
|
|
|
### Common Tasks
|
|
|
|
**Sync the schema to the database:**
|
|
```bash
|
|
npm run db:push
|
|
```
|
|
|
|
**Reset database (⚠️ deletes all data):**
|
|
```bash
|
|
npx prisma db push --force-reset
|
|
npm run db:seed
|
|
```
|
|
|
|
**Add new database tables/columns:**
|
|
1. Edit `prisma/schema.prisma`
|
|
2. Run `npm run db:push` to sync
|
|
|
|
Because there's no migration history, renaming or dropping a column that already holds data is destructive — `db push` will refuse (or require `--accept-data-loss`) rather than silently drop it. For a column that needs to change shape on a live database, do it in three steps: add the new column alongside the old one (non-destructive `db push`), run a one-off script to copy/transform the data into it, update the app code to use the new column, then drop the old column in a second `db push` once you've confirmed nothing still depends on it.
|
|
|
|
**Seed starter products:**
|
|
```bash
|
|
npm run db:seed
|
|
```
|
|
|
|
Updating the seed is safe — it only touches the 6 starter products by their slug, leaving your custom products untouched.
|
|
|
|
**Backup database:**
|
|
```bash
|
|
pg_dump -h <host> -p <port> -U <user> -d craft2prints -F p -f "database-backups/craft2prints_$(date +%Y-%m-%d_%H-%M-%S).sql"
|
|
```
|
|
`backup-database.ps1` in the repo root automates this — check its `$dbHost`/`$dbPort`/`$dbUser` match your actual `DATABASE_URL` before relying on it (it defaults to `localhost:5432`, which may not be where your database actually lives). **Backups only cover the database** — uploaded images/files live on disk under `public/uploads/` and need their own backup (see [Image & File Storage](#image--file-storage)).
|
|
|
|
---
|
|
|
|
## Image & File Storage
|
|
|
|
Every uploaded image or document — gallery photos, product photos, custom-request photos, design proofs, design-approval previews, order design previews, purchase invoices, expense receipts, and images added inside the design tool — is saved as a **file on disk**, not as base64 text in the database.
|
|
|
|
### How it works
|
|
|
|
1. A file arrives either as a `multipart/form-data` upload (admin forms, customer photo submissions) or as a base64 data URL generated client-side by the design tool's canvas export (`stage.toDataURL()`/`canvas.toDataURL()`).
|
|
2. `src/lib/storage.ts` decodes/receives it, and — unless it's a PDF — runs it through [`sharp`](https://sharp.pixelplumbing.com/) to resize (capped per category, never upscaled) and re-encode it (JPEG for photos, lossless PNG where transparency must be preserved, e.g. print-ready design previews).
|
|
3. The result is written to `public/uploads/<category>/<yyyy>/<mm>/<uuid>.<ext>` and the database column stores only the resulting path, e.g. `/uploads/gallery/2026/07/3f9c1e2a-....jpeg`.
|
|
|
|
Categories and their presets (`IMAGE_PRESETS` in `src/lib/storage.ts`):
|
|
|
|
| Category | Used for | Format | Max width |
|
|
|---|---|---|---|
|
|
| `gallery` | Public gallery photos | JPEG | 2000px |
|
|
| `products` | Product photos (main + per-color) | JPEG, or PNG if the source has transparency | 2000px |
|
|
| `custom-requests` | Customer-submitted photos | JPEG | 2000px |
|
|
| `proofs` | Admin-uploaded design proofs | JPEG | 2000px |
|
|
| `invoices` / `receipts` | Purchase/expense attachments | JPEG, or passthrough if PDF | 2000px |
|
|
| `designs` | Print-ready design previews (transparent) | PNG (lossless) | 4000px |
|
|
| `design-placements` | Design-on-product reference composites | JPEG | 2000px |
|
|
| `design-uploads` | Photos a customer adds inside the design tool | JPEG, or PNG if transparent | 3000px |
|
|
|
|
### Why this matters operationally
|
|
|
|
- **`public/uploads/` is gitignored but lives permanently on the server's disk.** It is not part of the database and is not covered by a `pg_dump` backup — back it up separately (a simple `rsync`/copy of the directory alongside your database backup schedule is enough).
|
|
- **No web server configuration is needed.** Next.js serves everything under `public/` automatically, in both `next dev` and `next start` — the same mechanism that already serves `public/logo.png`.
|
|
- **On a fresh server, make sure the Node process can write to `public/uploads/`** (it's created automatically on first upload, but the parent directory must be writable).
|
|
- **Uploads endpoint for the design tool**: images added inside the design canvas are uploaded via `POST /api/design-uploads` as soon as they're added, rather than embedded as base64 in the design's saved JSON — keeping cart/order/design-approval records small regardless of how many images a customer adds.
|
|
|
|
### Troubleshooting
|
|
|
|
**"sharp module could not be loaded" / native binding errors on the server**
|
|
Sharp ships platform-specific native bindings — never copy `node_modules` between machines (e.g. from a Windows dev box to a Linux server). Run a fresh `npm install` on the target machine (the standard deploy flow below already does this).
|
|
|
|
## Feature Setup
|
|
|
|
### Stripe Payments
|
|
|
|
1. Create account at https://dashboard.stripe.com
|
|
2. Switch to **Test Mode** (top-right toggle)
|
|
3. Get **Secret Key** from Developers > API keys
|
|
4. Set `STRIPE_SECRET_KEY` in `.env`
|
|
5. Start Stripe webhook listener (see Configuration Reference above)
|
|
6. Test with card: `4242 4242 4242 4242`, any future expiry, any 3-digit CVC
|
|
|
|
### Email Notifications
|
|
|
|
**Option 1: Gmail**
|
|
1. Enable 2-Factor Authentication
|
|
2. Generate app password at https://myaccount.google.com/apppasswords
|
|
3. Set SMTP variables in `.env`
|
|
4. Test by submitting a form that triggers email
|
|
|
|
**Option 2: Other Providers**
|
|
- Outlook, SendGrid, AWS SES, etc. — check their SMTP settings
|
|
- Most follow the same pattern: username (email), password (app-specific)
|
|
|
|
### CAPTCHA Protection
|
|
|
|
1. Sign up at https://dash.cloudflare.com
|
|
2. Go to Turnstile > Add Site
|
|
3. Copy **Site Key** and **Secret Key**
|
|
4. Set in `.env`:
|
|
```env
|
|
NEXT_PUBLIC_TURNSTILE_SITE_KEY="..."
|
|
TURNSTILE_SECRET_KEY="..."
|
|
```
|
|
5. Refresh the page — CAPTCHA should appear on forms
|
|
|
|
---
|
|
|
|
## Building for Production
|
|
|
|
### Build Process
|
|
|
|
```bash
|
|
# Build the Next.js app
|
|
npm run build
|
|
|
|
# Start the production server
|
|
npm start
|
|
```
|
|
|
|
The server listens on port 3000 by default.
|
|
|
|
### Build Output
|
|
|
|
```
|
|
.next/ # Compiled Next.js app (production build)
|
|
.next/standalone/ # Self-contained server
|
|
```
|
|
|
|
### Environment for Production
|
|
|
|
Before deploying, update `.env` for production:
|
|
|
|
```env
|
|
# Database (use production connection string)
|
|
DATABASE_URL="postgresql://user:pass@prod-db:5432/craft2prints"
|
|
|
|
# SECURITY: Change these!
|
|
ADMIN_USER="<strong-username>"
|
|
ADMIN_PASSWORD="<strong-password>"
|
|
ADMIN_SESSION_SECRET="<new-random-string>"
|
|
SESSION_SECRET="<new-random-string>"
|
|
|
|
# Stripe (switch to LIVE keys, not test)
|
|
STRIPE_SECRET_KEY="sk_live_..."
|
|
STRIPE_WEBHOOK_SECRET="whsec_live_..."
|
|
|
|
# Site URL
|
|
NEXT_PUBLIC_SITE_URL="https://yourdomain.com"
|
|
|
|
# Email (production SMTP)
|
|
SMTP_HOST="..."
|
|
SMTP_USER="..."
|
|
SMTP_PASSWORD="..."
|
|
MAIL_FROM="Craft2Prints <noreply@yourdomain.com>"
|
|
```
|
|
|
|
---
|
|
|
|
## Deployment
|
|
|
|
### Option 1: Self-Hosted (Recommended for Control)
|
|
|
|
**Hardware Requirements:**
|
|
- Server with Node.js 18+ support
|
|
- 2GB+ RAM (more for concurrent traffic)
|
|
- A reachable PostgreSQL database (local install, Docker container, or managed service)
|
|
- Persistent disk for `public/uploads/` — uploaded images/files live here, not in the database (see [Image & File Storage](#image--file-storage))
|
|
|
|
**Setup:**
|
|
|
|
1. **Upload code to server**
|
|
```bash
|
|
scp -r . user@server:/app/craft2prints
|
|
```
|
|
|
|
2. **Install dependencies**
|
|
```bash
|
|
ssh user@server
|
|
cd /app/craft2prints
|
|
npm install --production
|
|
npm run build
|
|
```
|
|
|
|
3. **Set up environment**
|
|
```bash
|
|
cp .env.example .env
|
|
# Edit .env with production values
|
|
nano .env
|
|
```
|
|
|
|
4. **Create database**
|
|
```bash
|
|
npm run db:push
|
|
npm run db:seed
|
|
```
|
|
|
|
5. **Install process manager** (keep app running)
|
|
```bash
|
|
npm install -g pm2
|
|
pm2 start "npm start" --name craft2prints
|
|
pm2 startup
|
|
pm2 save
|
|
```
|
|
|
|
6. **Set up reverse proxy** (nginx)
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name yourdomain.com;
|
|
|
|
location / {
|
|
proxy_pass http://localhost:3000;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection 'upgrade';
|
|
proxy_set_header Host $host;
|
|
proxy_cache_bypass $http_upgrade;
|
|
}
|
|
}
|
|
```
|
|
|
|
7. **Enable HTTPS** (Let's Encrypt)
|
|
```bash
|
|
sudo apt-get install certbot python3-certbot-nginx
|
|
sudo certbot --nginx -d yourdomain.com
|
|
```
|
|
|
|
### Option 2: Platform Services
|
|
|
|
**Vercel** (easiest, free tier available)
|
|
- Push to GitHub
|
|
- Connect GitHub repo to Vercel
|
|
- Environment variables in Vercel dashboard
|
|
- Automatic deploys on push
|
|
|
|
**Heroku** (deprecated but still works)
|
|
- Use Procfile + buildpack
|
|
- Set environment variables in Heroku dashboard
|
|
|
|
**Railway, Render, etc.**
|
|
- Similar to Vercel — push → deploy
|
|
|
|
### Option 3: Docker (Containerized)
|
|
|
|
Create `Dockerfile`:
|
|
```dockerfile
|
|
FROM node:18-alpine
|
|
WORKDIR /app
|
|
COPY package*.json ./
|
|
RUN npm ci --production
|
|
COPY . .
|
|
RUN npm run build
|
|
EXPOSE 3000
|
|
CMD ["npm", "start"]
|
|
```
|
|
|
|
```bash
|
|
docker build -t craft2prints .
|
|
docker run -p 3000:3000 --env-file .env -v craft2prints-uploads:/app/public/uploads craft2prints
|
|
```
|
|
The `-v` volume mount is required — without it, `public/uploads/` lives inside the container's writable layer and every uploaded image/file is lost when the container is recreated.
|
|
|
|
### Post-Deployment Checklist
|
|
|
|
- [ ] Admin password changed from default
|
|
- [ ] Session secrets regenerated
|
|
- [ ] Stripe keys switched to **live** (not test)
|
|
- [ ] Stripe webhook configured in dashboard
|
|
- [ ] HTTPS enabled with valid certificate
|
|
- [ ] Email SMTP tested (send test order confirmation)
|
|
- [ ] CAPTCHA working on public forms
|
|
- [ ] Database backups scheduled
|
|
- [ ] `public/uploads/` backups scheduled (separate from the database — see [Image & File Storage](#image--file-storage))
|
|
- [ ] Log monitoring set up
|
|
- [ ] Rate limiting verified
|
|
- [ ] Staging environment created for testing
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Development
|
|
|
|
**"Cannot find module '@prisma/client'"**
|
|
```bash
|
|
npx prisma generate
|
|
npm install
|
|
```
|
|
|
|
**Port 3000 already in use**
|
|
```bash
|
|
npm run dev -- -p 3001 # Use port 3001 instead
|
|
# Or find and kill process using port 3000
|
|
```
|
|
|
|
**Blank product catalog**
|
|
```bash
|
|
npm run db:seed # Load starter products
|
|
```
|
|
|
|
**Database out of sync / corrupted state**
|
|
```bash
|
|
npx prisma db push --force-reset # ⚠️ drops and recreates all tables
|
|
npm run db:seed
|
|
```
|
|
|
|
### Production
|
|
|
|
**App not starting**
|
|
```bash
|
|
npm start # Check console for errors
|
|
npm run build # Rebuild if necessary
|
|
```
|
|
|
|
**Email not sending**
|
|
```bash
|
|
# Test SMTP configuration
|
|
node -e "const smtp = require('nodemailer'); console.log(JSON.stringify(process.env, null, 2))"
|
|
```
|
|
|
|
**High memory usage**
|
|
- Check for memory leaks: `node --inspect start`
|
|
- Increase server RAM or split into multiple processes
|
|
- Consider database optimization for large datasets
|
|
|
|
**Database connection issues**
|
|
```bash
|
|
# Test connection and list tables
|
|
psql $DATABASE_URL -c '\dt'
|
|
```
|
|
|
|
---
|
|
|
|
## Maintenance
|
|
|
|
### Regular Tasks
|
|
|
|
**Daily:**
|
|
- Monitor error logs
|
|
- Check payment processing (Stripe dashboard)
|
|
|
|
**Weekly:**
|
|
- Backup database and `public/uploads/`
|
|
- Review admin activity logs
|
|
- Check storage usage
|
|
|
|
**Monthly:**
|
|
- Update npm dependencies: `npm outdated`
|
|
- Review security advisories: `npm audit`
|
|
- Analyze financial reports
|
|
|
|
**Quarterly:**
|
|
- Security audit
|
|
- Performance optimization
|
|
- Dependency updates
|
|
|
|
### Updating
|
|
|
|
```bash
|
|
# Pull latest code
|
|
git pull
|
|
|
|
# Update dependencies
|
|
npm install
|
|
|
|
# Sync database schema
|
|
npm run db:push
|
|
|
|
# Rebuild if needed
|
|
npm run build
|
|
```
|
|
|
|
---
|
|
|
|
## Security Hardening
|
|
|
|
### Before Going Live
|
|
|
|
1. **Change default credentials**
|
|
- Admin username/password
|
|
- Session secrets (32-byte random strings)
|
|
|
|
2. **Enable HTTPS**
|
|
- Get certificate from Let's Encrypt (free)
|
|
- Set `NEXT_PUBLIC_SITE_URL` to `https://yourdomain.com`
|
|
|
|
3. **Rate Limiting**
|
|
- Enabled by default on login/checkout
|
|
- Adjust in code if needed
|
|
|
|
4. **CAPTCHA**
|
|
- Recommended on all public forms
|
|
- Get free keys from Cloudflare Turnstile
|
|
|
|
5. **Email**
|
|
- Use app-specific passwords (not your actual password)
|
|
- Enable SMTP authentication
|
|
|
|
6. **Backups**
|
|
- Daily automated backups
|
|
- Test restore process quarterly
|
|
|
|
---
|
|
|
|
## Support & Issues
|
|
|
|
- **Questions?** Check this guide again or search existing issues
|
|
- **Bug report?** Create GitHub issue with reproduction steps
|
|
- **Feature request?** Open an issue for discussion
|
|
|
|
---
|
|
|
|
*Last updated: 2026-07-22*
|