Staging the Zuniga Shoals "Zuniga Days" Event Section: Additive Deployment Pattern with S3 Prefix Isolation
What Was Done
We deployed a new event marketing section ("Zuniga Days") to the zunigashoals.com staging environment without touching production. The approach used S3 prefix-based isolation — staging content lives at s3://zunigashoals.com/_staging/index.html while production remains at s3://zunigashoals.com/index.html. This allowed us to iterate rapidly, validate design and email capture integration, and keep a clean rollback path.
The deployment added ~212 lines of HTML and CSS across a single file, introduced a sticky navigation ribbon, six activity tiles with styled callouts, email capture via Google Apps Script, and wired up three open questions (naming, event dates, domain strategy) as draft placeholders for stakeholder input.
Technical Details: File Structure and Deployment Flow
Source and Target
- Source file:
/tmp/zuniga-staging.html— working copy with prod snapshot merged + new section HTML + new CSS rules - Production snapshot:
/tmp/zunigashoals.com__index.html__2026-05-26T17-31-10Z.prod-snapshot— timestamped backup taken before any changes - Target (S3):
s3://zunigashoals.com/_staging/index.html - Production (untouched):
s3://zunigashoals.com/index.html
Why Prefix Isolation?
Using a /_staging/ prefix keeps staging and production on the same CloudFront distribution (no DNS flip, no distribution swap needed). The S3 bucket routing rules already resolve zunigashoals.com/_staging/* to the staging prefix, and zunigashoals.com/ (root path) to production. This eliminates:
- CloudFront cache invalidation for the production path
- Route53 CNAME swapping
- Dual bucket management
- Risk of accidental production overwrites
A developer can visit https://zunigashoals.com/_staging/ to see staged changes immediately while prod traffic is unaffected.
What Was Added: Section Architecture
Sticky Ribbon
<div class="ribbon">
Zuniga Days · the last free open anchorage in San Diego
</div>
CSS rule: .ribbon — position fixed, top of viewport, z-index layered above hero content. Uses backdrop-filter: blur() for semi-transparent effect; no performance hit on modern browsers.
New Section: <section id="zd">
Inserted between the hero banner and shop section. Contains:
- Pull quote: "Who's in charge here?" — NO ONE. — Reinforces the "no permits, no fees, no rules" positioning
- Three-pillar callout: PERMIT none required · COST free to anchor · RULES see above — Simple, scannable grid
- Six activity tiles: Each a
<div class="zd-tile">with title and brief description:- Unsanctioned Dinghy Races
- Zuniga Shoals T-Shirt Contest
- Boat-Diving Contest
- Raftup & Rage
- Anchor & Anthem
- Flag Off Your Stern
- Value prop callout: "What you get for free" — Hotel Del / Coronado / Point Loma views
- Secondary CTA: "No boat? No problem" — encourages logistics (find a dinghy, crew up)
- Email capture form: Input + CTA button "Tell me the day. I'll be there."
Email Capture Integration
The form submits to the existing Google Apps Script endpoint (no new infrastructure). JavaScript handler:
document.querySelector('form.zd-email').addEventListener('submit', async (e) => {
e.preventDefault();
const email = e.target.querySelector('input[type="email"]').value;
const payload = {
action: "zuniga_days_signup",
email: email,
timestamp: new Date().toISOString()
};
// POST to existing GAS endpoint
await fetch(GAS_ENDPOINT, {
method: 'POST',
body: JSON.stringify(payload)
});
// Clear form / show confirmation
});
The action: "zuniga_days_signup" key allows the backend to bucket signups separately from other form submissions. No new Google Sheet, no new Apps Script — reuses existing infrastructure.
Infrastructure: S3, CloudFront, and Bucket Policy
S3 Bucket Configuration
The bucket zunigashoals.com uses routing rules in its static website configuration. When CloudFront receives a request to /_staging/index.html, the bucket resolves it correctly because:
- Both files exist in the same bucket root
- CloudFront origin is set to the S3 website endpoint (not the REST endpoint)
- S3 website routing does not require custom rules for prefix paths — the key is simply
_staging/index.html
CloudFront Distribution
No changes to distribution ID or behavior rules were needed. The distribution already caches both paths separately because CloudFront treats /index.html and /_staging/index.html as distinct cache keys. This means:
- Staging and production have independent TTLs and invalidation cycles
- An invalidation on
/_staging/*does not affect/index.htmlcaching - Staging can be deployed, tested, and redeployed without cache-busting prod
No Route53 Changes Required
zunigashoals.com A-record already points to the CloudFront distribution. The CNAME and distribution ID remain unchanged; traffic routing is handled at the S3 object-key level.
Key Decisions and Rationale
1. Additive-Only Changes to Source
Rather than editing production HTML in-place, we kept prod snapshot and merged all new markup into a staging copy. This ensures:
- Rollback is one S3 copy: If staging breaks, we redeploy the snapshot or the current prod file.
- Code review clarity: A diff between snapshot and staging shows exactly what was added.