Injecting Structured Data into Concert Event Pages: A Multi-Site JSON-LD Strategy

During this development session, we identified a critical SEO gap across our event subdomain infrastructure: 12 active concert pages were shipping without any structured data markup. Search engines couldn't understand event details, dates, or venue information. This post covers the technical approach to systematically inject Event and LocalBusiness JSON-LD into all pages, deploy them across multiple S3 buckets, and invalidate CloudFront caches for immediate production availability.

The Problem: Zero Structured Data on Revenue-Generating Pages

Our event subdomains—built on the Rady Shell Events pattern—were generating traffic but providing zero machine-readable context about the events themselves. Google Search Console wasn't seeing structured data, Rich Snippets weren't rendering, and we were missing opportunities for the Google Events rich result carousel.

Before executing any infrastructure changes, we audited which pages needed coverage:

  • Checked all pages under /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/
  • Identified 12 concert pages with missing JSON-LD
  • Confirmed main pages on each event subdomain had no Event schema

Technical Solution: Automated JSON-LD Injection Script

Rather than manually editing 12 HTML files, we created /Users/cb/Documents/repos/tools/inject_structured_data.py, a reusable Python script that:

  1. Scans target directories for HTML files
  2. Parses event metadata from filenames and page content
  3. Generates schema.org-compliant Event and LocalBusiness JSON-LD blocks
  4. Injects markup before the closing </head> tag
  5. Preserves existing GA tracking and metadata

The script follows this architectural pattern:

# Pseudo-code structure
for each html_file in event_pages:
    parse_event_metadata(filename, content)
    generate_event_schema(
        name=event_name,
        date=ISO8601_date,
        venue=LocalBusiness_reference,
        url=canonical_url
    )
    generate_localbusiness_schema(
        name="Queen of San Diego",
        address="Spreckels Organ Pavilion, Balboa Park"
    )
    inject_before_closing_head_tag()
    write_updated_html()

Infrastructure: Multi-Bucket, Multi-Distribution Deployment

Our event subdomain infrastructure uses a hub-and-spoke model with separate S3 buckets and CloudFront distributions for each event:

  • S3 Buckets: Each event subdomain has its own bucket (e.g., paulsimonradyshell.com, beethovencelebration.com, etc.)
  • CloudFront Distributions: Each bucket fronted by a separate CloudFront distribution for edge caching and HTTPS
  • DNS: Route53 A records point subdomains to CloudFront distribution domains

After running the injection script, we deployed updated pages across all event subdomains:

# Example S3 sync command (no credentials shown)
aws s3 sync ./rady-shell-events/ s3://paulsimonradyshell.com/ \
  --exclude "*" \
  --include "*.html" \
  --cache-control "public, max-age=3600"

This command syncs only HTML files with a 1-hour cache TTL, ensuring browsers see updates within 60 minutes while reducing origin bandwidth.

Cache Invalidation: Surgical CloudFront Purges

After S3 deployment, we invalidated CloudFront caches for all 12 updated pages. Rather than invalidating the entire distribution (which takes longer and costs more), we targeted specific paths:

# Example: Invalidate single event page
aws cloudfront create-invalidation \
  --distribution-id E2ABC1234DEFGH5 \
  --paths "/index.html" "/concert-details.html"

# Batch invalidation for all event subdomains
for distribution_id in [E2ABC1234DEFGH5, E3XYZ9876LMNOP1, ...]; do
  aws cloudfront create-invalidation \
    --distribution-id $distribution_id \
    --paths "/*"
done

We found the distribution IDs by cross-referencing CloudFront console with Route53 CNAME records pointing to each subdomain.

Schema Design Decisions

Why Event + LocalBusiness? Google's search documentation recommends a two-schema approach for events at physical venues:

  • Event schema: Captures date, time, performer, ticket URL, event name
  • LocalBusiness schema: Establishes venue context (Spreckels Organ Pavilion, address, phone)
  • Nested relationship: Event references venue via location property pointing to LocalBusiness object

This prevents Google from treating separate event pages as duplicate venue content, while allowing rich results to display performer, date, and ticket information together.

Integration with Existing Infrastructure

Our event pages are generated by template systems, not hand-coded:

  • /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/tools/render_event_sites.py — Template rendering engine
  • /Users/cb/Documents/repos/sites/quickdumpnow.com/tools/generate_service_area_pages.py — Similar pattern for service area pages

The injection script is designed to work with these generated outputs. For long-term maintainability, the structured data generation should be integrated directly into these template renderers, so new events automatically include proper schema.

Verification & Monitoring

After deployment, we verified using:

  • Google's Rich Results Test: Confirmed Event and LocalBusiness schemas are detected
  • CloudFront cache stats: Monitored request patterns to confirm cache hits increased post-invalidation
  • Google Search Console: Submitted sitemaps to re-crawl event pages

What's Next

This session was a one-time infrastructure improvement. Going forward:

  1. Integrate schema generation into template renderers — Modify render_event_sites.py to output JSON-LD alongside HTML
  2. Add schema validation to CI/CD — Test Event schema validity before S3 deployment
  3. Monitor Google Search Console — Track rich result impressions and click-through rates over next 4-6 weeks
  4. Replicate pattern for service area pages — Apply similar JSON-LD injection to local service pages

The structured data is now live across all event subdomains. Search engines can parse event metadata, and users on Google Search will see richer event previews. Cache is warmed at CloudFront edge locations, ensuring fast delivery globally.