Rafe Blandford

Adding Schema.org to 145k Static Pages

Rafe Blandford
3 min read
Adding Schema.org to 145k Static Pages
Photo by Chris Ried / Unsplash

When you're running a live CMS, adding structured data is usually a template change — edit the theme, add the JSON-LD block, deploy. Every page gets the schema automatically because they're all generated from the same templates.

When your site is 145,000 static HTML files spidered from a CMS that no longer exists, it's a different problem entirely.

The starting point

The All About Symbian archive — along with its sister sites All About Windows Phone and All About Mobile — was preserved as static HTML in early 2026. The spidering process captured every article, review, feature, podcast, and gallery page as individual HTML files. The content was intact, but the structured data was minimal: basic Article microdata from the original CMS, and BreadcrumbList JSON-LD that we'd added in a previous pass.

Then Google started sending emails.

What Google wanted

The Google Search Console alerts were specific:

  • Review snippets: "Invalid object type for field 'itemReviewed'" — 61 review pages had review-like content (scores, reviewer names) but no proper Review schema
  • Forum comments: "Missing field 'comment'" — the DiscussionForumPosting schema on forum pages lacked the comment array that Google now requires
  • Breadcrumbs: "data-vocabulary.org schema deprecated" — some pages still had the old breadcrumb format
  • Junk files: HTML files masquerading as images in upload directories

Each of these required a different approach, and none of them could be solved with a template change.

The post-processing approach

The solution was a set of Python scripts that read each HTML file, extract metadata from the existing markup, build the appropriate JSON-LD block, and inject it back into the file. Each script handles one schema type:

Review pages (4,504 across three sites): The original CMS had review scores and reviewer names in the HTML, but as Article microdata — not Review schema. The script extracts the product name from the <h1>, the score from a content attribute, the author from an itemprop element, and builds a proper Review JSON-LD with itemReviewed, reviewRating, and author as a structured Person object. It even distinguishes between Product reviews (hardware) and SoftwareApplication reviews based on keyword detection.

News and feature articles (42,001 pages): These became NewsArticle or Article depending on section — news and flow pages get NewsArticle (eligible for Top Stories), features get Article. Each includes headline, description, author (Person with name and URL), datePublished, image, publisher (Organization), and articleSection.

Podcast episodes (616 pages): These were upgraded from generic Article to PodcastEpisode, with AudioObject containing the actual MP3 URL, duration (extracted from existing HTML microdata), file size, and encoding format. The partOfSeries property links each episode to its podcast series, extracted from the og:audio:album meta tag.

Forum threads (81,117 pages): The existing DiscussionForumPosting schema was enhanced with a comment array containing the first page's reply posts as Comment objects, plus commentCount, articleSection, inLanguage, and isAccessibleForFree.

Gallery pages (147): Upgraded from Article to ImageGallery with ImageObject entries for each photo on the page.

Support/KB pages (193): Tagged as TechArticle with headline, author, and description extracted from the Q&A structure.

The edge cases

Static archives have edge cases that live sites don't:

  • Symlinks: The archive uses lowercase symlinks for case-insensitive URL handling. Every script needs to skip symlinks to avoid processing the same content twice — and to avoid the symlink inheriting changes from the real file unexpectedly.
  • Two sites, shared icons: Some icon files are used as both tbimg (100×100 thumbnail) and fimg (640×180 featured image) by different articles. You can't make the file both sizes. Solution: featured image size wins, CSS scales it down for thumbnails.
  • URL-encoded filenames: Files with %20 in the name coexist with files that have actual spaces in the name. Caddy URL-decodes requests, so it serves the space-in-name version. Both files need to be updated.
  • Placeholder detection: The archive has two different placeholder image files — a 782-byte 640×180 PNG and a 287-byte 100×100 PNG. Scripts need to check file size, not just filename.

The results

After a single session, the three archive sites had:

  • Review JSON-LD on every review page
  • Article/NewsArticle JSON-LD on every editorial page
  • PodcastEpisode on every media page
  • Enhanced DiscussionForumPosting on every forum thread
  • WebSite + Organization on every homepage
  • Person JSON-LD on every author page
  • Consistent BreadcrumbList everywhere

All from post-processing scripts that can be rerun if the source files change. The scripts are at github.com/rafeblandford — the approach generalises to any static archive.

What happened next

Within days of submitting the sitemaps for revalidation, Google's coverage reports started improving. The review snippet errors cleared first, then the breadcrumb warnings. The forum comment schema was accepted on recrawl. The structured data went from "basic Article microdata" to a comprehensive schema implementation covering 145,000 pages across six different content types.

The irony: this preserved archive from the early smartphone era now has more thorough schema markup than most actively-maintained websites.

New writing by email

Occasional pieces on product, technology and AI — and how they actually play out in practice.

Thanks — check your inbox to confirm.