```html

Renaming Guest Memorial Pages Without Breaking Photo Uploads: A Path-Based URL Alias Strategy

What We Did

We investigated renaming a guest page from queenofsandiego.com/g/2026-06-27-esmi to /g/2026-06-27-shumway-memorial while keeping the old URL live and unbroken. The investigation covered photo upload validation, barcode verification, CloudFront routing, and DynamoDB integrity. The solution: deploy a second S3 object at the new path pointing to identical HTML—no DNS changes, no Lambda modifications, zero blast radius.

How Guest Pages Work: The Architecture

Guest memorial pages are single-file HTML documents stored in S3 bucket queenofsandiego-web under the prefix g/. Each page is generated server-side and includes a hardcoded EVENT_ID embedded in JavaScript:

// Inside the HTML file
var EVENT_ID = "2026-06-27-esmi";
var PHOTO_CODE = "ZM9DMZ";

The page slug (e.g., 2026-06-27-esmi) becomes the URL, but the EVENT_ID is what matters for all backend operations. This is the critical insight that makes renaming safe.

Photo Upload Validation: DynamoDB as the Source of Truth

When a guest uploads a photo to the page, the browser:

  1. Extracts the embedded PHOTO_CODE from the HTML (e.g., ZM9DMZ)
  2. Sends the file to a Lambda presigner endpoint with that code
  3. The Lambda looks up the PHOTO_CODE in the DynamoDB table jada-crew-dispatch (region: us-east-1)
  4. Retrieves the matching event_id from that record
  5. Presigns an S3 upload URL to s3://jada-charters-photos/2026-06-27-esmi/ (using the EVENT_ID, not the URL slug)

The critical detail: the Lambda validates against the DynamoDB record, which contains the EVENT_ID, not the URL slug. The browser never reads the URL; it reads the HTML. Changing the URL path does not change the EVENT_ID inside the HTML, so uploads continue to work identically.

CloudFront Extensionless URL Resolution

Guest pages are served without a .html extension. This is handled by a CloudFront cache behavior and origin request Lambda:

  • CloudFront Distribution ID: EPF415U2AO8B3
  • Cache Behavior Pattern: /g/*
  • Cache Policy: CachingDisabled (no CloudFront caching per jada-no-cache-deploy)
  • Origin Request Function: Lambda@Edge that appends .html to the request URI before forwarding to S3

The rewrite is generic: any request to /g/ANYTHING becomes /g/ANYTHING.html at the S3 origin. This means a new S3 object at g/2026-06-27-shumway-memorial.html is instantly live at the new URL with zero configuration.

Guest Page Generation and Testing

Guest pages are generated server-side and committed to the repo at jada-estate/content/g/. The deployment pipeline:

  1. Developer adds (or copies) the HTML file to the repo g/ directory
  2. Runs jada-deploy (wrapper around AWS S3 sync)
  3. The CLI invokes CloudFront invalidation with no cache (per jada-no-cache-deploy)
  4. The test suite test_crew_pages.py automatically detects all files in g/ and validates their structure (event_id, photo_code, required HTML elements)

No manual registration, no database entry, no Route53 update—the test gate and deployment are fully automated.

Photo Code Validation: Why It Still Works

The photo code ZM9DMZ is tied to a DynamoDB item in jada-crew-dispatch:

AttributeValue {
  photo_code: "ZM9DMZ",
  event_id: "2026-06-27-esmi",
  ...other fields
}

When we copy the HTML to a new path (e.g., g/2026-06-27-shumway-memorial.html) without changing the embedded EVENT_ID or PHOTO_CODE, the DynamoDB lookup returns the same presigned S3 URL to s3://jada-charters-photos/2026-06-27-esmi/. Photos uploaded via the old URL and the new URL land in the same S3 prefix, are validated by the same admin approval Lambda, and are displayed by the same gallery JavaScript. Full bidirectional compatibility.

The Path-Based Alias Decision

The initial question was: "Can we use DNS to hide the old slug?" The answer is no—DNS operates at the hostname level, not the path level. Since the slug is in the path (not the hostname), the only solution is a second S3 object.

Why not rename the original?

  • The old URL was already texted to the guest and may be shared externally
  • Analytics, waiver confirmations, and trip sheet records reference the slug
  • Compliance filing §7117 was submitted with the original event_id

Why a second object instead of a redirect?

  • Redirects add latency and require additional HTTP round-trips
  • A second S3 object is zero cost and one-step deployment
  • Photo uploads work identically from both paths
  • If the old URL is ever removed, guests with the link still have access at the new path

Blast Radius: What We Verified

Every system that references guest pages was audited:

  • Photo uploads: Validate against EVENT_ID in DynamoDB, not URL slug—unaffected
  • Photo gallery display: Uses embedded EVENT_ID to fetch from S3 prefix—unaffected
  • Admin approval flow: Queries DynamoDB by event_id—unaffected
  • Waiver links: Generated with absolute URL; if updated, works at new slug—unaffected
  • Trip sheets: Link to old URL; both URLs serve identical content—unaffected
  • Google Analytics: Separate goals per slug; new slug accumulates new traffic; historical data on old slug remains—unaffected
  • Compliance filings (§7117): Keyed on event_id, not URL slug—unaffected

Deployment Example

To deploy the new URL:

# In jada-estate repo
cp content/g/2026-06-27-esmi.html content/g/2026-06-27-shumway-memorial.html
# Edit the HTML if you want to update the page title (optional)
jaja-deploy
# CloudFront is invalidated automatically; URL is live in ~3 seconds

The old URL remains live and functional indefinitely.

What's Next

The deployment is a 5-minute operation. Before deploying, confirm the honoree name with the client—the assessment identified that the current page memorializes a living client rather than the decedent, and a corrected sibling page already exists with the correct content.

```