Building a Daily Artist Spotlight System for Event Pages: Dynamic Content Injection with Google Apps Script and S3
We recently implemented a daily artist celebration feature for queenofsandiego.com's Rady Shell Events pages. The system needed to inject fresh, curated content about featured artists three times daily until each concert date, inspired by the principle that celebrating excellence drives engagement more effectively than commodity features alone.
What Was Built
The solution consists of three integrated components:
- ArtistCelebrationsService.gs: A Google Apps Script service that queries artist data and generates HTML content
- Content injection pipeline: Python tools that update static HTML files with dynamic artist sections
- Multi-region deployment: S3 + CloudFront infrastructure serving updated content to all event subdomains
The architecture treats artist spotlights as computed content rather than hardcoded sections. This allows daily updates without manual intervention while keeping the static-site generator approach intact.
Core Implementation: Google Apps Script Service
We created /apps-script-replacement/ArtistCelebrationsService.gs as the single source of truth for artist content. The service exposes a public endpoint that returns JSON data for any given event:
function doGet(e) {
const eventId = e.parameter.eventId;
const celebration = ArtistCelebrationsService.getArtistSpotlight(eventId);
return ContentService.createTextOutput(JSON.stringify(celebration))
.setMimeType(ContentService.MimeType.JSON);
}
The service queries a Google Sheet containing artist metadata (biography, achievements, accolades) and integrates with the Anthropic API to generate contextual, celebration-focused content blocks. The key decision here was separating data from presentation: the Apps Script returns structured data, not HTML, allowing multiple consumption patterns (server-side injection, client-side rendering, email newsletters).
The getArtistSpotlight() function:
- Retrieves the current artist assignment for the given event ID from the master sheet
- Fetches artist biographical data and recent achievements
- Calls Claude via the Anthropic API to generate a 150-200 word celebration paragraph emphasizing the artist's impact and artistry
- Caches results with a 4-hour TTL to minimize API calls
- Returns structured JSON with the generated content, artist image URL, and metadata
Static Site Integration: Content Injection Pipeline
Rather than making every event page dynamically fetch content (which would add client-side complexity and dependency on a constantly-running backend), we chose a pre-computed injection approach. This maintains the performance benefits of static HTML while enabling fresh content daily.
The injection pipeline consists of two Python scripts:
render_event_sites.py generates the initial event HTML from the Jinja2 template. We modified this script to insert a placeholder section:
<section class="artist-celebration" id="artist-spotlight">
<!-- Artist spotlight content injected here -->
</section>
inject_artist_spotlight.py (newly created) runs three times daily via a scheduled job. It:
- Queries the GAS deployment endpoint for current artist celebration content
- Loads all event HTML files from S3 buckets (one per event subdomain)
- Replaces the placeholder section with rendered HTML
- Uploads updated files back to S3
- Invalidates CloudFront caches to ensure users see fresh content within 60 seconds
The script pattern is idempotent: running it multiple times produces the same result, critical for reliability when scheduled tasks occasionally overlap or retry.
Infrastructure: Multi-Region S3 and CloudFront
The event pages are distributed across multiple S3 buckets, one per event subdomain. For example:
s3://rady-shell-events-summer2024/s3://rady-shell-events-fall2024/s3://rady-shell-events-special-guests/
Each bucket is fronted by a CloudFront distribution for edge caching and SSL termination. We maintain a mapping of event subdomains to S3 bucket names and CloudFront distribution IDs in the project configuration file at /agent_handoffs/projects/queenofsandiego_rady_shell_events.md.
The injection script invalidates CloudFront paths using the distribution ID and the /* wildcard pattern, ensuring cache invalidation completes within 60 seconds for all edge locations.
Content Freshness Strategy
The system updates three times daily (8 AM, 2 PM, 8 PM Pacific) via scheduled Lambda functions or cron jobs (depending on deployment method). This cadence provides:
- Morning update: Fresh content for users checking schedules before work
- Afternoon update: Refreshed content for midday browsers
- Evening update: Final content push before evening concert attendance
Content rotates daily until concert day, then locks to a final "artist bio" version. The GAS service respects concert dates from the events.json schema, automatically returning biographical rather than celebration content on concert day itself.
Design Decisions and Tradeoffs
Pre-computed vs. dynamic injection: We chose pre-computed injection over client-side dynamic loading because it avoids adding JavaScript dependencies, network round-trips, and potential API failures to user page loads. Static HTML pages remain fast and resilient.
Google Apps Script as the content engine: Apps Script provides easy integration with Google Sheets (our data store) and the Anthropic API while requiring no separate infrastructure. The deployment model (automatic versioning, built-in logging) simplified operations for a small team.
Placeholder injection pattern: Rather than complex HTML parsing and DOM manipulation, we use simple string replacement with HTML comments. This keeps the injection logic trivial and maintainable.
What's Next
Future enhancements include:
- A/B testing different celebration content styles to measure engagement impact
- Artist quote integration from social media sources
- Dynamic image rotation showing artist performance photos
- Analytics tracking on artist spotlight click-through rates
The architecture is extensible: adding new content types only requires new Anthropic prompts and additional placeholder sections in the template.
```