From ec12d0c51c2dca491c12dfbca3d72760849289d0 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 5 Aug 2026 21:05:30 +0900 Subject: [PATCH 1/2] Serve Markdown alternates for pages and news posts Every page and news post written in Markdown now gets a .md route next to its HTML page, with front matter stripped and Liquid rendered, and the HTML head advertises it via link rel=alternate. This makes the content directly consumable by LLM tools. --- _layouts/default.html | 1 + _plugins/markdown_alternates.rb | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 _plugins/markdown_alternates.rb diff --git a/_layouts/default.html b/_layouts/default.html index 23b9581ac3..423089cd63 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -68,6 +68,7 @@ + {% if page.markdown_url %}{% endif %} diff --git a/_plugins/markdown_alternates.rb b/_plugins/markdown_alternates.rb new file mode 100644 index 0000000000..a062ba439b --- /dev/null +++ b/_plugins/markdown_alternates.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +# Publishes the Markdown source of every page and news post next to its +# HTML page, so /en/about/ can also be fetched as /en/about.md. The copy +# is the source with the front matter stripped and Liquid evaluated, +# matching what the HTML page was rendered from. +module MarkdownAlternates + FRONT_MATTER = Jekyll::Document::YAML_FRONT_MATTER_REGEXP + + def self.documents(site) + site.pages + site.posts.docs + end + + def self.alternate_url(page) + return unless File.extname(page.relative_path) == ".md" + return unless page.url.end_with?("/") + + page.url == "/" ? "/index.md" : "#{page.url.chomp("/")}.md" + end + + def self.markdown_for(site, page) + raw = File.read(site.in_source_dir(page.path)) + raw = Regexp.last_match.post_match if raw =~ FRONT_MATTER + + payload = site.site_payload + payload["page"] = page.to_liquid + site.liquid_renderer.file(page.path) + .parse(raw) + .render!(payload, registers: { site: site, page: page }) + end +end + +# Runs before generators, so only hand-written pages and posts are +# tagged; pages generated later (news archives) get no alternate. +Jekyll::Hooks.register :site, :post_read do |site| + MarkdownAlternates.documents(site).each do |page| + url = MarkdownAlternates.alternate_url(page) + page.data["markdown_url"] = url if url + end +end + +Jekyll::Hooks.register :site, :post_write do |site| + MarkdownAlternates.documents(site).each do |page| + url = page.data["markdown_url"] + next unless url + + path = site.in_dest_dir(url.delete_prefix("/")) + FileUtils.mkdir_p(File.dirname(path)) + File.write(path, MarkdownAlternates.markdown_for(site, page)) + end +end From 060e4b4914adca3353dc13edefa4dcee1e50b8b9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 6 Aug 2026 08:24:16 +0900 Subject: [PATCH 2/2] Read Markdown sources as UTF-8 The Cloudflare build environment has no locale set, so the default external encoding is US-ASCII and reading non-ASCII sources broke the front matter match. Read with bom|utf-8 as Jekyll itself does. --- _plugins/markdown_alternates.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_plugins/markdown_alternates.rb b/_plugins/markdown_alternates.rb index a062ba439b..139896c154 100644 --- a/_plugins/markdown_alternates.rb +++ b/_plugins/markdown_alternates.rb @@ -19,7 +19,7 @@ def self.alternate_url(page) end def self.markdown_for(site, page) - raw = File.read(site.in_source_dir(page.path)) + raw = File.read(site.in_source_dir(page.path), encoding: "bom|utf-8") raw = Regexp.last_match.post_match if raw =~ FRONT_MATTER payload = site.site_payload