Extracting a Shared Modal Widget Across 17 Multi-Domain Subpages: Cross-Site Asset Deduplication at Scale
What We Did
We discovered that a sophisticated booking modal—originally built and tested on queenofsandiego.com—needed to be ported to 17 subpages across sailjada.com without duplicating its CSS, HTML, and JavaScript. The challenge: each subpage was independently generated from a template system, meaning the modal code existed in separate inline copies on every page. We extracted it into a shared, cacheable asset and deployed it across both staging and production with zero downtime.
Technical Details: The Problem
The modal was initially embedded inline on the queenofsandiego.com homepage. When we needed the same modal on sailjada.com's crew page and booking flow, a naive copy-paste would have created maintenance debt: any future modal fix would require patching across multiple files and deployments.
Worse, the initial subpage audit revealed:
- An f-string brace bug in the modal's embedded CSS (malformed template variable escaping)
- Relative path issues on QR code references (
./qr/zelle-qr.pngvs. absolute paths) - Missing global function scope for Stripe.js and Google Analytics callbacks
- 17 sailjada subpages with distinct byte-range locations for the broken modal block
Inline fixes would fail because the template generator—likely inject_structured_data.py or a similar bulk-injection script—was re-creating these pages on each deploy, overwriting manual patches.
Solution: Shared Asset Extraction
Step 1: Map the Modal Source
We pulled the full booking widget block from the queenofsandiego.com prod homepage and mapped its component structure:
- CSS block: ~180 lines of modal styling (flexbox, z-index layering, animation keyframes)
- HTML block: modal wrapper, date picker input, CTA buttons, QR code image tags
- JavaScript block: event listeners, Stripe integration, GA event firing, date validation
The JavaScript relied on three global functions that had to be available at runtime:
window.openBookingModal()— triggered by CTA button clickswindow.handleDateSelect(dateString)— calendar date selection handlerwindow.submitBookingForm()— Stripe redirect and GA tracking
Step 2: Build the Shared Asset
We created /assets/jada-modal.js as a single, minifiable JavaScript file that:
- Declared all modal CSS as a template literal (avoiding parse-time template variable expansion)
- Injected a
<style>tag into the DOM on first load - Defined the modal HTML structure and appended it to
document.body - Exposed the three global functions as an IIFE (Immediately Invoked Function Expression) to avoid polluting the global scope more than necessary
- Handled absolute paths for media assets (QR code image →
/assets/qr/zelle-qr.png)
The key insight: by injecting markup dynamically, we avoided the f-string brace bug entirely. The CSS and HTML were strings in JavaScript, not template variables.
Step 3: Update All 17 Subpages
We patched each of the 17 sailjada subpages by:
- Removing the inline modal block (CSS + HTML + broken JS)
- Adding a single
<script src="/assets/jada-modal.js"></script>tag - Verifying all CTA button elements matched the selector
button[data-action="book"]or equivalent class - Ensuring Stripe.js and Google Analytics were already loaded before the modal script fired
Affected subpages included /crew/, /book/, /service-areas/ (and child depth-1 subpages), and others totaling 17 HTML files on the sailjada.com S3 bucket.
Infrastructure & Deployment
S3 & CloudFront Strategy
The asset file was deployed to:
- S3 bucket:
sailjada-www(same bucket as subpages) - Key path:
assets/jada-modal.js - CloudFront distribution: The primary sailjada.com distribution (found via a targeted query on all CF distributions)
We staged the asset first, validated syntax via Node.js, then promoted. Critical step: we used precise path invalidation instead of wildcards:
aws cloudfront create-invalidation \
--distribution-id XXXXXXXXXXXXXX \
--paths /assets/jada-modal.js \
/crew/ /crew/index.html \
/book/ /book/index.html \
... (all 17 subpage paths)
Wildcard invalidations (/*) would have been cheaper in principle but risky; precise paths guaranteed we only cleared what we modified and avoided unnecessary cache thrashing.
Staging Validation
Before prod promotion, we:
- Deployed to a staging S3 bucket and CF distribution
- Verified
https://staging-sailjada.com/assets/jada-modal.jsreturned 200 and valid JavaScript syntax - Smoke-tested a crew page subpage through CF DNS, confirming modal opened and date picker worked
- Checked browser console for JavaScript errors, GA event fires, and Stripe.js availability
- Verified QR image paths resolved correctly (no 404s)
Key Decisions & Trade-offs
Why a Single Shared Asset vs. Web Components?
We chose a traditional IIFE + DOM injection over Web Components because:
- Sailjada subpages run on older browsers (GA indicated some IE11 traffic)
- No build pipeline exists for subpage templates; IIFE injection is backward-compatible
- Web Components would require a separate
jada-modal-element.jsand custom element registration; added complexity for minimal benefit
Absolute vs. Relative Paths
The original modal used relative paths (./qr/zelle-qr.png), which worked on the homepage but broke on subpages in subdirectories like /crew/. We switched to absolute paths (/assets/qr/zelle-qr.png) and verified the S3 bucket structure matched the web root.
CSS Injection Timing
Rather than include <link rel="stylesheet" href="..."> (which