SEO/AEO fixes: valid JSON-LD, llms.txt, modified dates, descriptions, 404 page - #125
Merged
Merged
Conversation
The per-post Article block used Liquid's `escape` filter, which HTML-escapes (Don't -> Don't) rather than JSON-escapes, and emitted folded `description: >` values verbatim, leaving a raw newline inside the JSON string literal. 46 of 49 posts shipped structured data that no JSON parser would accept. Render every content-derived string with `jsonify` (and `strip` the description) so the output is always valid JSON, and add `bun run check:jsonld`, which parses every ld+json block in _site and requires @context/@type, wired into the html-proofer job next to the CSP check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PGCnJxq4L3ShzTP9km88L
A Liquid-templated /llms.txt in the llmstxt.org format: site summary, every post (newest first) with its description, plus the standing pages and feeds. Descriptions pass through normalize_whitespace so a folded frontmatter value containing a paragraph break still renders as a single link line; the Article JSON-LD description now gets the same treatment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PGCnJxq4L3ShzTP9km88L
Introduce an optional `last_modified_at` post frontmatter field that feeds JSON-LD `dateModified`, the `article:modified_time` OG tag, the sitemap `<lastmod>`, and the Atom `<updated>` element, all falling back to `date`. Listing pages (home, archive, tags) take the newest post's date as their lastmod; about and linkfarm carry none rather than a misleading build time. The frontmatter check validates the field's format and that it is not before the publish date. Set `timezone: UTC` so zone-less frontmatter dates render the same in local builds as in CI. Local builds in US Eastern were shifting every post to the previous evening, which the macOS visual baselines had captured (a 2023-03-26 post rendered as "March 25, 2023"); regenerate the six baselines whose diff crossed the tolerance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PGCnJxq4L3ShzTP9km88L
Replace the placeholder site description ("Personal site of Ryan
Spletzer"), which the homepage, archive, tags index, and about page all
fell back to for their meta and og:description, and which the feeds
and llms.txt reuse. The homepage, archive, and tags index now carry
their own description frontmatter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PGCnJxq4L3ShzTP9km88L
Add a short pointer to the archive, tags, and home, plus the five most recent posts, so a dead inbound link still lands somewhere navigable. Give it a description and mark it noindex (GitHub Pages serves it at /404.html with a 200, so it is otherwise crawlable). The page now lists posts, so its visual test switches to a viewport screenshot like the other listing pages to avoid baseline churn on every new post; macOS baselines regenerated. 404.md joins archive.md and tags.md in the markdownlint ignore list because of the Liquid/HTML loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PGCnJxq4L3ShzTP9km88L
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-ups from auditing the site against Anthony Nocentino's SEO/AEO post. Most of the checklist was already covered; this branch closes the gaps.
ArticleJSON-LD on 46 of 49 posts.escapeHTML-escaped values (Don't) and foldeddescription: >values carried a raw newline into the JSON string literal, so no JSON parser accepted the block. Content-derived strings now render viajsonify(withnormalize_whitespace | stripon descriptions). Newbun run check:jsonldparses everyld+jsonblock in_siteand runs in thehtml-prooferjob next tocheck:csp.llms.txt(llmstxt.org format): site summary, every post with its description, pages, and feeds.last_modified_atpost frontmatter feeds JSON-LDdateModified,article:modified_time, sitemap<lastmod>, and Atom<updated>, falling back todate; validated bycheck:frontmatter.timezone: UTCso local builds render zone-less frontmatter dates the same as CI. The macOS baselines had captured the local-zone shift (a 2023-03-26 post rendered as "March 25, 2023"); the six that crossed the diff tolerance are regenerated.noindex. Its visual test switches to viewport capture like the other listing pages, with macOS and Linux baselines regenerated.Deliberately not done:
Article→BlogPosting(Google treats them identically) andnoindexon thin posts.Test plan
bun run check:jsonld— 344 blocks across 147 pages parse; against the pre-fix build it flagged exactly the 46 broken postscheck:frontmatter(incl. valid/invalidlast_modified_atcases),typecheck,check:csp,markdownlint-cli2,actionlint,htmlprooferbun run test:visuallocally — 56 passed, 4 skipped🤖 Generated with Claude Code
https://claude.ai/code/session_018PGCnJxq4L3ShzTP9km88L