Building a Daily Artist Celebration Feature: From Google Apps Script to S3 Distribution
We recently implemented a dynamic artist celebration feature across the Rady Shell Events subdomain network for queenofsandiego.com. The goal was inspired by Nike's marketing philosophy: rather than simply listing event details, celebrate the artists themselves with fresh content that updates three times daily until concert day. This post walks through the architecture, implementation, and deployment strategy we used.
The Problem We Solved
Each event page needed a dedicated artist spotlight section that would:
- Update automatically three times per day
- Continue updating until the day of the concert
- Pull from a centralized data source (Google Sheets)
- Distribute to dozens of event subdomains without manual intervention
- Cache properly across CloudFront without stale content issues
Architecture Overview
The solution consists of four integrated layers:
- Data Layer: Google Sheets as the source of truth for artist celebrations
- API Layer: Google Apps Script providing a REST endpoint
- Build Layer: Python scripts that inject spotlight HTML into static event pages
- Distribution Layer: S3 buckets and CloudFront distributions for each event subdomain
Google Apps Script Implementation
We created a new file ArtistCelebrationsService.gs in the apps-script-replacement project that handles all artist celebration logic:
// File: /Users/cb/Documents/repos/sites/queenofsandiego.com/
// rady-shell-events/apps-script-replacement/ArtistCelebrationsService.gs
function getArtistCelebration(eventName) {
// Fetches celebration data from Google Sheets
// Returns formatted HTML for injection into event pages
// Filters data based on event date and current time
}
This service connects to the same Google Sheets instance used for event metadata. The key decision here was to keep artist celebrations in the same sheet rather than creating a separate data source—this reduced complexity and ensured all event metadata stayed synchronized.
We updated Code.gs to expose a new route handler for artist celebrations:
// New route in Code.gs
doGet(e) {
if (e.parameter.action === 'getArtistCelebration') {
return ContentService.createTextOutput(
JSON.stringify(getArtistCelebration(e.parameter.event))
).setMimeType(ContentService.MimeType.JSON);
}
// ... existing routes
}
We then created a new GAS deployment specifically for the artist celebration endpoint. This was deployed to a production environment and the deployment ID was recorded for use in our build scripts.
Python Build Pipeline
Two Python scripts work together to inject the artist spotlight into static HTML:
Script 1: render_event_sites.py (Enhanced)
We modified the existing /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/tools/render_event_sites.py to call the new GAS endpoint during the build phase. This script:
- Iterates through all events defined in events.json
- Calls the GAS artist celebration endpoint for each event
- Injects the returned HTML into a template placeholder on each event page
Script 2: inject_artist_spotlight.py (New)
We created a new utility script at /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/tools/inject_artist_spotlight.py that provides focused functionality for the spotlight injection:
- Reads the pre-rendered event HTML files
- Locates the injection point (a comment anchor in the HTML template)
- Inserts the artist celebration section at the correct location in the DOM
- Writes updated HTML back to disk for S3 upload
This separation of concerns made the pipeline more testable and allowed the render script to focus on fetching data while the injection script focused on HTML manipulation.
S3 and CloudFront Distribution Strategy
The Rady Shell events are distributed across multiple event subdomains, each with its own S3 bucket and CloudFront distribution. We identified all event buckets and distributions:
// List all event S3 buckets
aws s3 ls | grep rady-shell-event
// Expected output (example)
rady-shell-event-beethoven-2024
rady-shell-event-vivaldi-2024
rady-shell-event-mozart-2024
// ... many more
For each bucket, we uploaded the updated HTML files with the injected artist spotlight section:
aws s3 cp ./rendered-events/ s3://rady-shell-event-beethoven-2024/ \
--recursive \
--exclude "*" \
--include "*.html" \
--cache-control "max-age=3600"
We set max-age=3600 (1 hour) for HTML files. This ensures CloudFront will revalidate content frequently, allowing the three-times-daily updates to propagate within an hour of each refresh cycle.
Cache Invalidation Strategy
After uploading updated HTML to all S3 buckets, we needed to invalidate CloudFront caches. First, we identified all distribution IDs:
aws cloudfront list-distributions \
--query "DistributionList.Items[?contains(DomainName, 'rady-shell-event')].Id" \
--output text
Then, for each distribution, we created an invalidation to clear cached HTML:
aws cloudfront create-invalidation \
--distribution-id E1EXAMPLE2DIST3 \
--paths "/*.html" "/events/*.html"
The wildcard approach ensured that even pages we hadn't explicitly modified would be refreshed, preventing stale content from serving to users.
Why This Architecture?
Google Apps Script as the Source: GAS provides a lightweight REST API that can query Google Sheets without maintaining a separate backend. For a feature that updates three times daily, this eliminates infrastructure overhead.
Static Site Generation: Rather than making client-side API calls on every page load, we generate the HTML server-side during build time. This improves page load performance and SEO, and ensures content is always present (no JavaScript dependency).
S3 + CloudFront: This combination provides global distribution, CDN caching for performance, and cheap storage. By keeping HTML cache TTL relatively short (1 hour), we balance performance with freshness.
Three Daily Updates: The build and deploy pipeline runs three times daily via a scheduled task (cron job or similar). This matches the product requirement without requiring real-time infrastructure.
Deployment Process
The complete deployment workflow is:
- Scheduled task triggers the build pipeline
render_event_sites.pycalls the GAS endpoint for each eventinject_artist_spotlight.pyinserts the HTML into templates