Staging a Dynamic Event Section Without Prod Downtime: Zuniga Days Launch Infrastructure
This post covers the technical approach we used to roll out a new "Zuniga Days" event section to zunigashoals.com while keeping production untouched and maintaining a safe staging environment for iteration. The challenge: add ~212 lines of CSS, new HTML section markup, and a form submission handler to an existing site without risking the live domain.
What We Built
The Zuniga Days section is a multi-component feature landing on the homepage:
- A sticky top ribbon announcing the event
- A new hero section with three-pillar callouts (PERMIT, COST, RULES)
- Six activity tiles describing race types and social events
- Email capture form that posts to an existing Google Apps Script endpoint
- Contextual CTAs linking to existing shop inventory for event merchandise
All of this had to integrate cleanly into the existing page structure without breaking any current functionality or requiring a CloudFront cache invalidation on the production path.
Technical Architecture: The Staging Strategy
Rather than risk deploying directly to s3://zunigashoals.com/index.html, we used an additive prefix strategy with S3 path-based routing:
Production: s3://zunigashoals.com/index.html
Staging: s3://zunigashoals.com/_staging/index.html
CloudFront: Both paths served through same distribution (no new CF resources needed)
The CloudFront distribution for zunigashoals.com was already configured with an S3 origin pointing to the bucket root. Because CloudFront passes the full path through to S3, creating a /_staging/ prefix required zero additional infrastructure changes. This meant we could:
- Deploy to staging without affecting the live site
- Test form submissions and styling in the exact same CDN context
- Roll back by simply deleting the staging file if needed
- Promote to production by updating the S3 object ACL or using a CloudFront alias swap (not needed here)
File Organization and Content Structure
The working file was created at:
/tmp/zuniga-staging.html
Before any edits, we took a production snapshot:
/tmp/zunigashoals.com__index.html__2026-05-26T17-31-10Z.prod-snapshot
This snapshot served as both a rollback point and a diff baseline. The actual modifications were 30+ iterative edits to /tmp/zuniga-staging.html, each adding or refining:
- CSS variables and utility classes prefixed with
.zd*(Zuniga Days namespace) to avoid collisions with existing styles - The ribbon element using sticky positioning with z-index layering
- A new semantic
<section id="zd">element positioned between the hero and shop sections - Email form markup with
data-action="zuniga_days_signup"attributes for tracking - Inline JavaScript event handler wiring form submissions to the existing GAS endpoint
Form Integration Without New Backend Infrastructure
The email capture form needed to post user signups somewhere. Rather than provisioning a new backend service, we leveraged the existing Google Apps Script endpoint already handling other site interactions.
The approach:
// Capture form submit event
document.getElementById('zuniga-email-form').addEventListener('submit', async (e) => {
e.preventDefault();
const email = e.target.querySelector('input[type="email"]').value;
// Post to existing GAS endpoint with action identifier
await fetch(GAS_ENDPOINT_URL, {
method: 'POST',
body: JSON.stringify({
action: 'zuniga_days_signup',
email: email,
timestamp: new Date().toISOString()
})
});
});
By namespacing the action as zuniga_days_signup, the GAS script can route this request to the correct handler without creating a new service. This keeps the architecture simple and reuses existing instrumentation.
CSS Namespace Strategy
To prevent style conflicts with the existing site, all new classes were prefixed with .zd:
.zd-ribbon { /* sticky top banner */ }
.zd-section { /* main event section */ }
.zd-pillar { /* three-column callout */ }
.zd-tile { /* activity card */ }
.zd-email-form { /* signup form container */ }
This namespace isolation meant we could add ~120 lines of CSS without running the BDD (behavior-driven development) risk of accidentally breaking existing elements through cascade conflicts or specificity wars.
Deployment Pipeline and Verification
The deployment was executed in this order:
- Snapshot production: Downloaded current
index.htmlfrom prod bucket and stored locally with timestamp - Stage to S3: Uploaded modified HTML to
s3://zunigashoals.com/_staging/index.htmlusing S3 API (no credentials embedded in commands) - Verify CloudFront propagation: Hit
https://zunigashoals.com/_staging/index.htmland confirmed content served with correctContent-Type: text/htmlheaders - Sanity checks:
- Inspected DOM for new
#zdsection presence - Confirmed sticky ribbon visibility on scroll
- Tested email form submission logged correct action to GAS endpoint
- Verified no broken image references or 404s in console
- Checked that existing nav, hero, and shop sections rendered unchanged
- Inspected DOM for new
- Document state: Updated progress kanban with staging URL and open questions for stakeholders (naming, dates, domain registration)
No CloudFront cache invalidation was needed because the staging path had never been cached before—it's a new object in S3, so TTL starts fresh.
Open Questions and Next Steps
The staging deployment is live, but several decisions remain open:
- Event name: "Zuniga Days" is the working title. Alternatives: "Ocean Man," "Shoals Racing," or a domain-branded variant.
- Event dates: Email capture is configured, but actual dates need input from the Chloe/event-planning team before promoting to production.
- Sister domain: Should we register
zunigadays.com,lastfreeanchor.com, or another variant? Domain registration would add a Route53 record pointing to this same CloudFront distribution. - Merchandise integration: The section CTAs reference "annual event tee drops," but the shop grid doesn't yet have a yearly-edition product slot. That's a separate inventory change in the Shopify/product backend.
Side Note: Progress Board Agent Issue
During this work, we discovered the