Deploying Zuniga Days: Additive Frontend Changes and S3 Staging Strategy
This post covers the technical approach to shipping a new "Zuniga Days" event section to zunigashoals.com without risking production, using S3 staging prefixes and snapshot-based rollback patterns.
What Was Done
We added a full event-marketing section to the Zuniga Shoals homepage—including sticky ribbon branding, activity tiles, email capture, and CTAs—entirely through additive HTML/CSS deployed to a staging prefix. The production index.html was never modified; instead, we:
- Snapshotted the current production HTML to
/tmp/zunigashoals.com__index.html__2026-05-26T17-31-10Z.prod-snapshot - Built the new section in a staging copy at
/tmp/zuniga-staging.html - Deployed to
s3://zunigashoals.com/_staging/index.html(non-production path) - Verified the staging URL served the updated content before any prod cutover
Technical Details: The Additive Approach
The new "Zuniga Days" section added approximately 212 lines of code:
- Sticky ribbon: CSS for
.ribbonclass—fixed positioning, z-index management, responsive breakpoints - Section markup: New
<section id="zd">inserted between the hero and shop elements in the DOM - Component CSS: Scoped classes
.zd*(e.g.,.zd-pillar,.zd-tile,.zd-email-capture) to avoid cascade conflicts - Email capture: Form that POSTs to the existing Google Apps Script endpoint with payload
action:"zuniga_days_signup"
The CSS was written to be self-contained; no existing styles were overridden. This means reverting the change is as simple as removing the new <section> and the .zd*` + `.ribbon` rule blocks.
Infrastructure: S3 Staging Prefix Pattern
Rather than versioning files or using a separate staging bucket, we used a path-based staging strategy:
Production: s3://zunigashoals.com/index.html
Staging: s3://zunigashoals.com/_staging/index.html
This approach has several advantages:
- Single bucket, single CloudFront distribution: No need to manage separate S3 buckets or CF distributions for staging
- Same origin: Staging and prod share the same domain and CDN, avoiding cross-origin issues during testing
- Cache isolation: The
/_staging/prefix is distinct enough in CF that cache keys don't collide with prod - No CF invalidation overhead: We don't invalidate the production path; only staging is updated
The CloudFront distribution (exact distribution ID withheld for security) is configured to:
- Serve
s3://zunigashoals.comas the origin - Honor the bucket's directory structure in the cache key
- Apply standard TTLs (typically 3600s for HTML) per CloudFront defaults
To verify staging was live, we fetched:
curl https://zunigashoals.com/_staging/index.html | grep -i "zuniga days"
Key Decisions
Why Snapshot Before Deploy?
We saved a timestamped snapshot of production to /tmp/zunigashoals.com__index.html__2026-05-26T17-31-10Z.prod-snapshot as an immediate rollback artifact. If staging tests revealed issues, we could:
- Compare diffs against prod to isolate what broke
- Rapidly restore prod from the snapshot without an S3 API call
- Audit exactly what was in flight during the window
This is cheaper and faster than relying on S3 versioning and CloudFront invalidation for emergency rollbacks.
Why Additive, Not Mutation?
We never overwrote production. Instead, all changes landed in the staging prefix first. This means:
- Production traffic is unaffected until explicit promotion
- The deploy is idempotent; re-deploying staging multiple times is safe
- A/B testing or gradual rollout can happen by updating DNS or CF rules later
Why Google Apps Script for Email Capture?
The email-capture form POSTs to an existing GAS endpoint already running on the site. We didn't create a new backend; instead, we:
- Reused the existing
action:"zuniga_days_signup"message type in the GAS handler - Avoided new infrastructure, new secrets, or new authentication
- Kept email data flowing to the same ingestion pipeline (likely Google Sheets or a similar GAS sink)
The form includes standard client-side validation (required fields) and posts JSON to avoid URL-encoding complexity.
What's Next
Open items before promoting staging to production:
- Event name confirmation: "Zuniga Days" is the working title; decision needed on final branding (competing with "Burning Man" theme)
- Date finalization: The section currently says "lands soon"; need Chloe's input on actual event dates
- Sister domain setup: Consider registering
zunigadays.com,zunigashoalsdays.com, orlastfreeanchor.comand creating Route53 alias records pointing back to the main site or a dedicated landing page - Annual tee SKU: The section CTAs to a "Zuniga Days annual edition tee"; that product tile in the shop grid hasn't been created yet
- Production promotion: Once approved, copy
s3://zunigashoals.com/_staging/index.htmltos3://zunigashoals.com/index.htmland invalidate the CF cache for/index.html
Additionally, there's a separate infrastructure issue: the progress-board agent daemon isn't processing tickets from https://progress.queenofsandiego.com/. The kanban state is stored as a static state.json on S3, but no daemon is currently polling it. This should be addressed in a follow-up post once the ticket routing is fixed.