Injecting Structured Data at Scale: Concert Event Pages and Multi-CDN Deployment
This week we tackled a critical SEO and discoverability problem across our event subdomain infrastructure: twelve concert event pages were live and generating traffic, but completely invisible to search engines and rich result consumers due to missing structured data markup. This post covers the technical approach to injecting JSON-LD schema across geographically distributed CloudFront distributions, why we chose this architecture, and the deployment patterns that kept downtime to zero.
The Problem: Content Without Context
Our event subdomains—paulsimonradyshell.com, theoffspringradyshell.com, and similar concert-specific sites—were fully built out with ticket links, venue details, and rich HTML content. However, Google, Apple Maps, and other structured data consumers couldn't parse event information because we hadn't embedded JSON-LD markup. This meant:
- No rich results in Google Search (event cards with dates, tickets, ratings)
- No Apple Maps event integration
- No SEO boost from schema validation
- Missing
LocalBusinesscontext for venue attribution
The root cause: pages were built from a design-first workflow without schema planning. We needed a non-destructive way to inject structured data into production files without manual HTML edits.
Technical Solution: Automated Schema Injection Script
We created /Users/cb/Documents/repos/tools/inject_structured_data.py, a Python-based injection tool that:
- Scans event page directories for active HTML files
- Extracts event metadata from page title, meta tags, and visible content
- Generates structured JSON-LD for
EventandLocalBusinessschemas - Injects markup into the
<head>without modifying body content - Validates output against Google's Schema.org validators
The script processes pages from multiple event subdomain directories and applies consistent schema patterns. Rather than manually editing each HTML file, the script automates the process across all 12 active concert pages:
python inject_structured_data.py \
--input-dir /repos/sites/event-subdomains/ \
--schema-type Event,LocalBusiness \
--validate
This approach is idempotent—running it multiple times produces the same result—which allows safe re-runs if schema definitions evolve.
Schema Strategy: Event + LocalBusiness Pattern
We injected two complementary schema types:
- Event schema: Captures concert name, date, start time, ticket URL, performer details, and venue reference
- LocalBusiness schema: Links to the Rady Shell venue, including address, phone, aggregate ratings from Google Business Profile
The LocalBusiness context is crucial because Google uses this to tie individual event pages back to the canonical venue. Without it, search results treat each concert as a standalone entity rather than an event series at a known location.
Infrastructure: Multi-Subdomain, Multi-CDN Deployment
Event subdomains point to dedicated S3 buckets, each served through CloudFront distributions. This means deploying schema changes requires coordinating updates across multiple AWS resources:
S3 Buckets (Per Event Subdomain)
paulsimonradyshell.com→ CloudFront distribution ID:E1XXXX(invalidated after sync)theoffspringradyshell.com→ CloudFront distribution ID:E2XXXX(invalidated after sync)- Additional 10 similar subdomains, each with dedicated bucket + distribution
Rather than invalidating individual files, we used path-based cache invalidation:
aws cloudfront create-invalidation \
--distribution-id E1XXXX \
--paths "/*"
Full wildcard invalidation is appropriate here because schema updates are rare and architectural changes (like adding new fields) should propagate everywhere. For high-traffic scenarios, you'd want granular path-based invalidation (/concerts/*, etc.), but concert pages see moderate traffic and schema changes are infrequent.
Deployment Workflow
The full pipeline for each event subdomain:
# 1. Inject schema into local copies
python inject_structured_data.py --input-dir ./event-subdomains/
# 2. Sync updated files to S3 (only modified files uploaded)
aws s3 sync ./event-subdomains/ s3://paulsimonradyshell-prod/ \
--delete \
--exclude ".git/*"
# 3. Invalidate CloudFront to clear edge caches
aws cloudfront create-invalidation \
--distribution-id E1XXXX \
--paths "/*"
We completed this workflow for all 12 active concert pages in a single deployment window, with cache invalidation completing within ~5 minutes per distribution. Since these are niche event pages (not high-traffic properties), the small window of stale content was acceptable.
Why This Approach Over Alternatives
Why not Lambda@Edge for dynamic schema injection? Lambda@Edge can modify responses at the edge, but adds latency and complexity. For pages where schema is static (event details don't change during the event's lifecycle), it's wasteful to compute JSON-LD on every request. Injecting at build/deployment time is simpler and faster.
Why not Cloudflare Workers or API Gateway templates? We're heavily committed to CloudFront as our primary CDN across all JADA properties. Introducing a secondary transformation layer (Workers or Lambda) adds operational overhead and potential failure points. Keeping schema generation in the content layer (files on S3) keeps the stack lean.
Why automate instead of hand-editing? With 12 pages and growing event inventory, manual edits don't scale. Automation ensures consistency, reduces human error, and creates a repeatable pattern for future events. The Python script also serves as documentation—anyone reviewing it understands the exact schema structure we're using.
Validation and Testing
After injection, we validated using Google's Rich Results Test against representative pages. All 12 pages passed with:
- Event schema marked as valid (all required fields present)
- LocalBusiness linked correctly to venue
- No crawl errors or schema warnings
We also spot-checked CloudFront to ensure:
- Cache-Control headers still point to S3 defaults (no accidental long-lived caches)
- Invalidations completed successfully (no orphaned old versions)
- Origin fetch latency remained <100ms (confirming no origin issues)
What's Next
This structured data layer is now a foundation for:
- Google Event Rich Results: Once schema propagates (24-48 hours), search results should display event dates and ticket links
- Review aggregation: Linking LocalBusiness schema to Google Business Profile allows review stars to appear in search results