Files
craft2prints/SETUP_AND_BUILD.md
AndymickandClaude Sonnet 5 396347b982 Move uploaded images to disk storage, fix Next 16 async params/searchParams
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>
2026-07-22 20:56:40 +01:00

16 KiB

Complete Setup & Build Guide

This document covers everything needed to develop, build, and deploy Craft2Prints.

Table of Contents

  1. Local Development Setup
  2. Configuration Reference
  3. Database Management
  4. Image & File Storage
  5. Feature Setup
  6. Building for Production
  7. Deployment
  8. Troubleshooting

Local Development Setup

Prerequisites

Node.js 18 or later is required:

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

# Clone the repository
git clone <your-repo-url>
cd craft2prints

# Install dependencies
npm install
# This automatically runs: npx prisma generate

Step 2: Environment Setup

# Copy example environment file
cp .env.example .env

Edit .env and configure at minimum:

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:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Step 3: Database Setup

# 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

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):

STRIPE_SECRET_KEY="sk_test_..."  # From Developers > API keys
STRIPE_WEBHOOK_SECRET="whsec_..."  # From Stripe CLI (see below)

Local Webhook Setup:

# 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:

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):

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

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:

npm run db:push

Reset database (⚠️ deletes all data):

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:

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:

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

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 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:
    NEXT_PUBLIC_TURNSTILE_SITE_KEY="..."
    TURNSTILE_SECRET_KEY="..."
    
  5. Refresh the page — CAPTCHA should appear on forms

Building for Production

Build Process

# 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:

# 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

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)

Setup:

  1. Upload code to server

    scp -r . user@server:/app/craft2prints
    
  2. Install dependencies

    ssh user@server
    cd /app/craft2prints
    npm install --production
    npm run build
    
  3. Set up environment

    cp .env.example .env
    # Edit .env with production values
    nano .env
    
  4. Create database

    npm run db:push
    npm run db:seed
    
  5. Install process manager (keep app running)

    npm install -g pm2
    pm2 start "npm start" --name craft2prints
    pm2 startup
    pm2 save
    
  6. Set up reverse proxy (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)

    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:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
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)
  • Log monitoring set up
  • Rate limiting verified
  • Staging environment created for testing

Troubleshooting

Development

"Cannot find module '@prisma/client'"

npx prisma generate
npm install

Port 3000 already in use

npm run dev -- -p 3001  # Use port 3001 instead
# Or find and kill process using port 3000

Blank product catalog

npm run db:seed  # Load starter products

Database out of sync / corrupted state

npx prisma db push --force-reset  # ⚠️ drops and recreates all tables
npm run db:seed

Production

App not starting

npm start  # Check console for errors
npm run build  # Rebuild if necessary

Email not sending

# 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

# 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

# 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