From 0169245f45063d9d49ce38ca3f0d87f57f2be3ce Mon Sep 17 00:00:00 2001 From: Elie Habib Date: Thu, 26 Mar 2026 12:48:56 +0400 Subject: [PATCH] feat(seo): BlogPosting schema, FAQPage JSON-LD, extensible author system (#2284) * feat(seo): BlogPosting schema, FAQPage JSON-LD, author system, AI crawler welcome Blog structured data: - Change @type Article to BlogPosting for all blog posts - Author: Organization to Person with extensible default (Elie Habib) - Add per-post author/authorUrl/authorBio/modifiedDate frontmatter fields - Auto-extract FAQPage JSON-LD from FAQ sections in all 17 posts - Show Updated date when modifiedDate differs from pubDate - Add author bio section with GitHub avatar and fallback Main app: - Add commodity variant to middleware VARIANT_HOST_MAP and VARIANT_OG - Add commodity.worldmonitor.app to sitemap.xml - Shorten index.html meta description to 136 chars (was 161) - Remove worksFor block from index.html author JSON-LD - Welcome all bots in robots.txt (removed per-bot blocks, global allows) - Update llms.txt: five variants listed, all 17 blog post URLs added * fix(seo): scope FAQ regex to section boundary, use author-aware avatar - extractFaqLd now slices only to the next ## heading (was: to end of body) preventing bold text in post-FAQ sections from being mistakenly extracted - Avatar src now derived from DEFAULT_AUTHOR_GITHUB constant (koala73) only when using the default author; custom authors fall back to favicon so multi-author posts show a correct image instead of the wrong profile --- blog-site/src/content.config.ts | 4 ++ blog-site/src/layouts/Base.astro | 6 ++- blog-site/src/layouts/BlogPost.astro | 72 +++++++++++++++++++++---- blog-site/src/pages/posts/[...id].astro | 5 ++ blog-site/src/styles/global.css | 43 +++++++++++++++ index.html | 5 +- middleware.ts | 7 +++ public/llms.txt | 24 ++++++++- public/robots.txt | 23 +------- public/sitemap.xml | 6 +++ 10 files changed, 158 insertions(+), 37 deletions(-) diff --git a/blog-site/src/content.config.ts b/blog-site/src/content.config.ts index 51da360c7..4a579d6b2 100644 --- a/blog-site/src/content.config.ts +++ b/blog-site/src/content.config.ts @@ -10,6 +10,10 @@ const blog = defineCollection({ keywords: z.string(), audience: z.string(), pubDate: z.coerce.date(), + modifiedDate: z.coerce.date().optional(), + author: z.string().optional(), + authorUrl: z.string().url().optional(), + authorBio: z.string().optional(), heroImage: z.string().optional(), }), }); diff --git a/blog-site/src/layouts/Base.astro b/blog-site/src/layouts/Base.astro index d1cca347b..348333c9c 100644 --- a/blog-site/src/layouts/Base.astro +++ b/blog-site/src/layouts/Base.astro @@ -12,9 +12,10 @@ interface Props { section?: string; jsonLd?: Record; breadcrumbLd?: Record; + faqLd?: Record; } -const { title, description, metaTitle, keywords, ogType, ogImage, publishedTime, modifiedTime, author, section, jsonLd, breadcrumbLd } = Astro.props; +const { title, description, metaTitle, keywords, ogType, ogImage, publishedTime, modifiedTime, author, section, jsonLd, breadcrumbLd, faqLd } = Astro.props; const pageTitle = metaTitle || title; const resolvedOgImage = ogImage || 'https://www.worldmonitor.app/favico/og-image.png'; const resolvedOgType = ogType || 'website'; @@ -68,6 +69,9 @@ const keywordTags = keywords ? keywords.split(',').map(k => k.trim()).slice(0, 6 {breadcrumbLd && (