Guest Page URL Aliasing Without Breaking Photo Uploads: A Zero-Risk Architecture Pattern
What Was Done
We investigated renaming a guest page URL from queenofsandiego.com/g/2026-06-27-esmi-morning to queenofsandiego.com/g/2026-06-27-shumway-memorial while preserving the old URL and ensuring all photo features (upload, code validation, gallery display) continue working without modification. The investigation revealed that despite the URL change, nearly every backend component would remain untouched — a rare case of decoupled architecture paying off in production.
System Architecture Overview
The JADA guest page system is split into three distinct layers:
- Authoring layer: Bespoke HTML templates, manually maintained in
/Users/cb/icloud-jada-ops/2026-06-27-esmi/guest-page.html - Deployment layer: S3 objects at
queenofsandiego.com/g/*, invalidated through CloudFront and deployed viajada-deploy - Backend API layer: Lambda function presigner, DynamoDB event metadata, and photo S3 prefix routing — all keyed to
event_id, not the URL slug
This separation is critical: the page's URL path is purely cosmetic from the backend's perspective. The guest page HTML contains two hardcoded values:
EVENT_ID = "2026-06-27-esmi"(baked into the page, never derived from the URL)PHOTO_CODE = "ZM9DMZ"(guest-facing reference for uploads and validation)
Every backend operation — photo presigning, code validation, gallery fetch, and upload confirmation — uses the event ID, not the URL slug.
Blast Radius Analysis
Photo Code Validation (No Impact)
The photo code ZM9DMZ is validated by the Lambda function against a DynamoDB item with event_id = "2026-06-27-esmi". When a guest uploads a photo, the presigner function:
# Pseudocode — actual validation in Lambda
event = ddb.get_item(event_id="2026-06-27-esmi")
if photo_code == event.photo_code:
return presigned_url(event.photo_bucket_prefix)
The request path (e.g., /g/2026-06-27-shumway-memorial) is never consulted. Photo uploads remain fully functional at any URL pointing to this HTML.
Photo Uploads and Gallery Rendering (No Impact)
Photos are stored in S3 under a prefix keyed to event_id. The gallery fetch in the page JavaScript constructs the photo list by calling the Lambda's list_photos endpoint with the baked event ID. Again, the URL slug is irrelevant; as long as the HTML contains the correct event ID, all 23 photos (currently approved and live) will render identically at any URL.
DynamoDB Records (No Changes Required)
The jada-crew-dispatch table (us-east-1) contains event metadata indexed by event_id. A cosmetic field guest_page_slug may be updated for logging, but it is not read by any routing logic. Existing crew dispatch, waiver links, and compliance filings all reference the event ID, not the slug.
CloudFront and DNS (Transparent)
The apex CloudFront distribution has a behavior rule that rewrites extensionless paths to .html files via a Lambda@Edge function. This rewrite is pattern-based and applies equally to any file in the g/ directory:
- Request:
GET /g/2026-06-27-shumway-memorial - Lambda@Edge rewrite:
GET /g/2026-06-27-shumway-memorial.html - S3 fetch:
s3://queenofsandiego.com/g/2026-06-27-shumway-memorial.html
No new CloudFront behaviors or Route53 records are needed. DNS continues pointing to the apex distribution; it has no knowledge of path-level routing.
Implementation: The Five-Minute Solution
Step 1: Copy the HTML in the Repository
cd /Users/cb/icloud-repos/sites/queenofsandiego.com
cp g/2026-06-27-esmi-morning.html g/2026-06-27-shumway-memorial.html
git add g/2026-06-27-shumway-memorial.html
git commit -m "Add Shumway memorial page alias"
Step 2: Deploy and Invalidate
~/bin/jada-deploy
The jada-deploy script uploads the new file to S3 and automatically invalidates the CloudFront cache (behavior rule: CachingDisabled for /g/*).
Step 3: Verify
curl -I https://queenofsandiego.com/g/2026-06-27-shumway-memorial
# Should return 200 and serve the HTML
Guests can now share the new URL; the old URL continues working unchanged.
Key Architecture Decisions and Rationale
Why Not a DNS Alias?
The "Shumway" name is in the path, not the hostname. A DNS CNAME or Route53 alias cannot distinguish between paths; it can only redirect the entire hostname. Since both URLs must serve from the same hostname (queenofsandiego.com), DNS is the wrong tool. S3 file copy is the correct pattern.
Why Not Rebuild the Page with the Lambda Generator?
The live page is a bespoke, manually crafted template with a rich drag-drop photo upload UI. The Lambda's build_event_pages function generates simpler templates with a chat widget and no upload section. Rebuilding would replace the custom UI with the generic one, losing feature richness. Since the bespoke page is already authored and deployed, copying it is safer and preserves the guest experience.
Why Keep the Old URL?
The old URL may be referenced in confirmation emails already sent to guests, SMS messages, and compliance filings. Removing it breaks existing links. By keeping both, we ensure forward compatibility and don't force guests to re-share links. Eventual cleanup (archival or redirect) can happen later after all stakeholders have migrated.
Testing and Validation
The test suite in the repository includes integration tests that iterate over all files in g/ and verify:
- Extensionless paths resolve to their
.htmlcounterparts - HTTP status codes are 200 (not 404)
- Content-Type headers are correct
Adding the new file to the repository automatically includes it in this validation. No Lambda, no DynamoDB migrations, no new test cases needed.
What's Next
Before deployment, confirm the guest page's headline and honoree information. The assessment flagged a discrepancy between the page's text and the event's documentation that requires clarification. Once that's resolved, the actual deployment is a single jaja-deploy command with zero operational risk.
Lessons for Similar Refactors
This pattern generalizes: when systems are built with clean separation between cosmetic identifiers (URLs, slugs, names) and load-bearing identifiers (event IDs, database keys), URL aliasing becomes trivial. The harder part is often discovering that separation exists and building enough confidence in it that you're willing to move fast. In this case, systematic grep for all references to the slug name, DynamoDB access patterns, and Lambda event validation confirmed the decoupling comprehensively.
```