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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e23f201d3d
commit
396347b982
+65
-22
@@ -7,10 +7,11 @@ This document covers everything needed to develop, build, and deploy Craft2Print
|
||||
1. [Local Development Setup](#local-development-setup)
|
||||
2. [Configuration Reference](#configuration-reference)
|
||||
3. [Database Management](#database-management)
|
||||
4. [Feature Setup](#feature-setup)
|
||||
5. [Building for Production](#building-for-production)
|
||||
6. [Deployment](#deployment)
|
||||
7. [Troubleshooting](#troubleshooting)
|
||||
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)
|
||||
|
||||
---
|
||||
|
||||
@@ -25,6 +26,8 @@ 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
|
||||
@@ -46,7 +49,7 @@ cp .env.example .env
|
||||
|
||||
Edit `.env` and configure at minimum:
|
||||
```env
|
||||
DATABASE_URL="file:./dev.db"
|
||||
DATABASE_URL="postgresql://user:password@localhost:5432/craft2prints?schema=public"
|
||||
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
|
||||
ADMIN_USER="admin"
|
||||
ADMIN_PASSWORD="changeme"
|
||||
@@ -62,7 +65,8 @@ node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
||||
### Step 3: Database Setup
|
||||
|
||||
```bash
|
||||
# Create SQLite database and run migrations
|
||||
# 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
|
||||
@@ -87,7 +91,7 @@ Open http://localhost:3000 in your browser.
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `DATABASE_URL` | SQLite database path | `file:./dev.db` |
|
||||
| `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 |
|
||||
@@ -147,17 +151,18 @@ 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
|
||||
|
||||
**Create database and migrations:**
|
||||
**Sync the schema to the database:**
|
||||
```bash
|
||||
npm run db:push
|
||||
```
|
||||
|
||||
**Reset database (⚠️ deletes all data):**
|
||||
```bash
|
||||
rm dev.db
|
||||
npm run db:push
|
||||
npx prisma db push --force-reset
|
||||
npm run db:seed
|
||||
```
|
||||
|
||||
@@ -165,6 +170,8 @@ npm run db:seed
|
||||
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
|
||||
@@ -174,11 +181,47 @@ Updating the seed is safe — it only touches the 6 starter products by their sl
|
||||
|
||||
**Backup database:**
|
||||
```bash
|
||||
cp dev.db dev.db.backup-$(date +%Y%m%d-%H%M%S)
|
||||
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
|
||||
@@ -274,7 +317,8 @@ MAIL_FROM="Craft2Prints <noreply@yourdomain.com>"
|
||||
**Hardware Requirements:**
|
||||
- Server with Node.js 18+ support
|
||||
- 2GB+ RAM (more for concurrent traffic)
|
||||
- SQLite storage, or PostgreSQL for larger deployments
|
||||
- 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:**
|
||||
|
||||
@@ -366,8 +410,9 @@ CMD ["npm", "start"]
|
||||
|
||||
```bash
|
||||
docker build -t craft2prints .
|
||||
docker run -p 3000:3000 --env-file .env 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
|
||||
|
||||
@@ -379,6 +424,7 @@ docker run -p 3000:3000 --env-file .env craft2prints
|
||||
- [ ] 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
|
||||
@@ -406,10 +452,9 @@ npm run dev -- -p 3001 # Use port 3001 instead
|
||||
npm run db:seed # Load starter products
|
||||
```
|
||||
|
||||
**Database corrupted**
|
||||
**Database out of sync / corrupted state**
|
||||
```bash
|
||||
rm dev.db
|
||||
npm run db:push
|
||||
npx prisma db push --force-reset # ⚠️ drops and recreates all tables
|
||||
npm run db:seed
|
||||
```
|
||||
|
||||
@@ -434,10 +479,8 @@ node -e "const smtp = require('nodemailer'); console.log(JSON.stringify(process.
|
||||
|
||||
**Database connection issues**
|
||||
```bash
|
||||
# Test connection
|
||||
psql $DATABASE_URL # If PostgreSQL
|
||||
# Or
|
||||
sqlite3 dev.db ".tables" # If SQLite
|
||||
# Test connection and list tables
|
||||
psql $DATABASE_URL -c '\dt'
|
||||
```
|
||||
|
||||
---
|
||||
@@ -451,7 +494,7 @@ sqlite3 dev.db ".tables" # If SQLite
|
||||
- Check payment processing (Stripe dashboard)
|
||||
|
||||
**Weekly:**
|
||||
- Backup database
|
||||
- Backup database and `public/uploads/`
|
||||
- Review admin activity logs
|
||||
- Check storage usage
|
||||
|
||||
@@ -521,4 +564,4 @@ npm run build
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-03-17*
|
||||
*Last updated: 2026-07-22*
|
||||
|
||||
Reference in New Issue
Block a user