```html

Building a Dynamic Artist Celebration Feature: From Google Sheets to Live Event Pages

We recently implemented a dynamic artist celebration feature for the Queen of San Diego Rady Shell Events website. The system automatically injects contextual artist information into event pages and refreshes this content up to three times daily until concert day. This post details the architecture, implementation decisions, and infrastructure changes required to ship this feature across dozens of event subdomains.

The Vision: Celebrating Artists Like Nike Celebrates Athletes

The core insight driving this feature comes from a Nike marketing principle: great brands don't sell commodities—they celebrate excellence. By prominently featuring artist spotlights on each event page, we shift focus from transactional concert details to the artistry and achievement of the performers themselves. This required a multi-layered technical solution spanning Google Apps Script, Python templating, S3 distribution, and CloudFront caching.

Architecture Overview

The solution consists of four main components:

  • Google Apps Script (GAS) Backend: A serverless endpoint that fetches artist data from a Google Sheet and generates celebration content
  • Python Site Renderer: Build-time template injection that seeds initial spotlight sections into static HTML
  • S3 + CloudFront Distribution: Event-specific subdomains serving static HTML with embedded JavaScript for dynamic updates
  • Client-Side Refresh Logic: JavaScript that polls the GAS endpoint on a schedule to keep content fresh

Google Apps Script Service: ArtistCelebrationsService

We created a new GAS module at `/apps-script-replacement/ArtistCelebrationsService.gs` that handles all artist data retrieval and HTML generation. This service:

  • Queries a designated Google Sheet containing artist bios, images, and celebration content
  • Formats the data into consistent HTML markup ready for injection
  • Validates artist entries and handles missing data gracefully
  • Serves content via a public HTTP endpoint with CORS enabled

The main entry point is a `doGet()` function that routes requests by event ID:

function doGet(e) {
  const eventId = e.parameter.eventId;
  const artistData = fetchArtistCelebration(eventId);
  return ContentService.createTextOutput(JSON.stringify(artistData))
    .setMimeType(ContentService.MimeType.JSON);
}

This endpoint is deployed as a new GAS deployment (separate from the main event site generator) to allow independent scaling and updates. The deployment URL structure remains hidden from the public, accessed only through our internal CloudFront distribution configuration.

Build-Time Injection via Python

During the site build process, we modified `/tools/render_event_sites.py` to inject a placeholder spotlight section into every event page template. This serves two purposes:

  • SEO & Progressive Enhancement: Search engines and users without JavaScript see meaningful content immediately
  • Render Stability: Avoids layout shift and provides a fallback if the GAS endpoint is unavailable

We also created a dedicated injection script at `/tools/inject_artist_spotlight.py` that runs post-render to update all existing event HTML files across our S3 buckets. This allowed us to retrofit the feature to 40+ existing event pages without regenerating the entire site.

The injection process:

python3 inject_artist_spotlight.py --event-bucket queenofsandiego-events --inject-template spotlight-section.html

This script discovers all event HTML files in the specified S3 bucket, locates the insertion point (a comment marker in the template), and injects the spotlight HTML consistently across all pages.

Infrastructure: S3, CloudFront, and Cache Invalidation

Each event subdomain (e.g., `nov-15-2024.queenofsandiego.com`) maps to a dedicated S3 bucket and CloudFront distribution. We updated the following resources:

  • S3 Buckets: Event HTML files were uploaded to buckets named `queenofsandiego-events-{event-slug}` with versioning enabled to support rollback
  • CloudFront Distributions: Cache policies were configured with TTL of 3600 seconds (1 hour), allowing the refresh schedule to be meaningful
  • Cache Invalidation: After uploading new HTML files, we invalidated all distribution caches using batch CloudFront invalidation calls

The cache invalidation strategy was critical here. Rather than invalidating single files, we issued distribution-wide invalidations using the path pattern `/*` to ensure all event pages received the updated spotlight markup:

aws cloudfront create-invalidation \
  --distribution-id {DISTRIBUTION_ID} \
  --paths "/*"

We discovered approximately a dozen CloudFront distributions during this process. Route53 DNS records already pointed event subdomains to CloudFront, so no DNS changes were required.

Client-Side Update Schedule

The client-side JavaScript polls the GAS endpoint three times daily until the concert date. The refresh interval is calculated at page load time based on the event date:

  • Pre-Concert: Refresh every 8 hours (3 times daily)
  • Concert Day: No additional refreshes (content is final)
  • Post-Concert: No refreshes (historical content)

This design minimizes API calls while keeping content fresh through the pre-concert period. The polling mechanism is implemented as a simple `setInterval()` with exponential backoff if the GAS endpoint experiences transient failures.

Key Technical Decisions

Why a Separate GAS Deployment? The original GAS project handled event page generation and email notifications. By isolating the artist celebration service into a separate deployment, we achieved:

  • Independent versioning and rollback capability
  • Isolation of Anthropic API calls for artist description generation
  • Ability to hotfix artist data without redeploying the main site generator

Why Python Injection Rather Than Build-Time GAS? We chose post-render Python injection because:

  • Decouples content updates from site generation (the main build happens once daily)
  • Allows emergency spotlight fixes without a full site rebuild
  • Simplifies the GAS codebase by keeping it focused on data retrieval, not templating

Why Static HTML + Dynamic Polling Rather Than Fully Client-Side Rendering? This hybrid approach balances:

  • SEO and accessibility (initial content is server-rendered)
  • Content freshness (client updates via polling)
  • Cache efficiency (static HTML is cacheable; only the spotlight section updates)

Deployment Process

The full rollout involved these steps:

  1. Deploy `ArtistCelebrationsService.gs` as a new GAS project
  2. List existing GAS deployments to identify the live exec URL ID
  3. Update the main GAS project router (`Code.gs`) to include routes for the artist celebration endpoint
  4. Render the site and inject spotlight sections into all event HTML files
  5. Upload