← Back to Blog
nextjssupabasereactengineering

Engineering a Custom Wedding Photo Collector

April 28, 2026 · 6 min read

Cathy and I have been best friends for over a decade. When she and Dylan started planning their wedding, I wanted to figure out how to contribute my engineering background to the celebration.

They hit a common snag during planning: collecting the candid photos guests take on their phones is surprisingly difficult.

Existing third-party platforms were rigid, heavily compressed incoming photos, or required guests to download an app and register an account. I set out to build an alternative that ran directly in mobile browsers via a table QR code and uploaded uncompressed original files, including raw iPhone .heic stills and videos.

Serverless Stack and Media Storage

A wedding application experiences virtually all of its traffic in a four-hour spike over unreliable venue Wi-Fi. I chose Next.js 14 (App Router) deployed on Vercel for the frontend and edge middleware, paired with Supabase for backend infrastructure.

Supabase Postgres handles submission metadata and real-time moderation state, while Supabase Storage ingests the media files directly. To catch misconfigurations during build and deploy pipelines, runtime validation verifies all database and bucket environment variables at application startup. This setup comfortably handled gigabytes of uncompressed uploads without ongoing infrastructure overhead.

Direct-to-Storage Uploads via Signed URLs

Most public photo platforms rely on client-side compression, downscaling mobile captures to small JPEG files in the browser before network transmission. I wanted to retain original sensor files while keeping the underlying object storage private.

Early iterations allowed direct browser uploads to an open-insert bucket gated purely by Row Level Security (RLS). To harden security without proxying gigabytes of video through Next.js serverless functions, I implemented a signed URL architecture. When a guest submits an upload, the browser calls an edge route (POST /api/upload) that validates session parameter sanity against directory traversal and returns a temporary, time-scoped upload token generated by the Supabase service role.

The frontend UploadForm component takes this token and streams original files directly from the mobile operating system to a private wedding-photos bucket. This flow supports native iPhone .heic stills alongside video formats like .mp4, .mov, and .webm. Because venue networks often drop connections during large video transmissions, a custom uploadPhotoWithRetry helper implements automatic exponential backoff for interrupted cellular requests.

The botanical-inspired upload form for guests

The client-side upload UI parses local .heic captures into instant thumbnail previews in the DOM before network transfer begins. Mobile layout spacing and explicit touch targets replace traditional desktop hover states so guests can review or remove individual photos effortlessly on small screens.

Multi-Layered Abuse Protection

Opening a public web gateway for anonymous file uploads invited immediate spam and automated abuse. Instead of relying solely on database rules, I configured Next.js Edge Middleware (src/middleware.ts) and targeted route handlers to intercept unvalidated traffic before it hits Postgres.

An environment-driven event window lock (NEXT_PUBLIC_EVENT_START and END) evaluates incoming traffic timestamps at the network edge. Any request arriving outside the wedding weekend is intercepted and rejected with a 403 Forbidden response before querying Postgres or generating storage tokens. Because edge caching can serve stale time evaluations, time checks bypass static HTTP cache headers entirely.

Rate limiting presented an interesting network constraint. Standard IP-based throttling would immediately block legitimate guests since dozens of mobile phones often share a single venue network NAT gateway or local Wi-Fi router. To prevent accidental lockouts, the rate limiter tracks individual sessions using cryptographically signed, HTTP-only browser cookies.

At the application API boundary (POST /api/submissions), explicit Origin and Referer checks verify cross-site requests against allowed production hostnames. Strict JSON payload byte limits prevent memory exhaustion attempts, while unauthenticated requests to administrative dashboards are redirected instantly at the middleware layer.

Curation Dashboard and Chunked Archiving

As guest submissions grew into thousands of photos and video clips, the administrative interface required performance optimizations to prevent DOM lag and database timeout errors.

The main review view (/admin/dashboard) implements server-side cursor pagination to load photo grids instantly without pulling total table counts into memory. An upgraded SubmissionCard component renders inline HTML5 video players alongside photo thumbnails. Curators can remove individual blurry shots or duplicate video clips without purging an entire guest submission. When the last file is deleted from a record, automated database triggers cleanly scrub the parent entry from Postgres.

Bulk downloading presented another browser limitation. Attempting to bundle gigabytes of original .heic and .mov media into a single ZIP archive inside a mobile browser consistently crashes tab memory limits. Using jszip and file-saver, the dashboard dynamically segments archive exports into memory-safe client-side batches of 50 items. This allows reliable, crash-free bulk archiving directly in standard web browsers.

Botanical Typography and Print Templates

Cathy and Dylan asked for an aesthetic centered on natural botanical colors rather than standard tech dashboards.

I bypassed default UI frameworks and configured Amarante for structured headings alongside Baskervville for editorial prose using next/font/google. The interface palette draws from muted olive green, forest green, terracotta, and blush pink. Because dark mode inversions would ruin the warm, off-white background coloration on mobile browsers, system theme preferences are explicitly overridden to enforce light mode across all devices.

To bridge the physical venue with the web app, I imported the qrcode library to generate vector-sharp, custom-colored QR identifiers. A bundled print-ready HTML template (table-sign.html) enabled the couple to print 5x7 physical table signage directly from their browser with accurate font kerning and reliable scanner optical contrast.

Pre-Event Testing and Verification

You cannot easily debug code or redeploy migrations during a live wedding ceremony. The application had to operate without active monitoring from the moment guests entered the venue.

To prepare for real-world devices, a Vitest suite tests core business logic including file extension validation, dynamic pagination calculations, runtime environment schema verification, and Origin rejection inside edge middleware. For integration testing, an automated Playwright harness simulates full mobile visitor journeys across Desktop Chromium, Mobile Chrome, and Mobile Safari. These E2E tests interact with touch layouts, select multi-file inputs, verify rejection of unsupported binary MIME types, and exercise admin authentication flows.

Over time, interrupted cellular uploads can leave unreferenced objects in cloud buckets. A scheduled Node.js reconciliation script (scripts/check_orphans.mjs) periodically audits Postgres tables against Supabase Storage bucket listings to detect and delete orphaned media blobs automatically.

Building an engineering tool for a personal celebration changes how you evaluate complexity. When system uptime directly impacts physical event logistics, you quickly learn to value simple serverless edge routes, strict boundary rate limits, and crash-safe browser uploads over unnecessary architectural abstractions. The application ran untouched throughout the weekend, collecting hundreds of candid photos without a single dropped connection or customer support text.