{"article":{"slug":"making-my-blog-readable-by-ai-agents","title":"Making My Blog Readable By AI Agents","subtitle":null,"summary":"Jake Marsh explains how he made his static nginx blog Deallocated Objects agent-friendly: markdown copies of every post with Accept-header content negotiation, llms.txt and llms-full.txt, an explicit robots.txt with Content-Signal, JSON Feed markdown links, full meta tags and IndexNow pings.","content_type":"tutorial","language":"en","canonical_url":"https://deallocatedobjects.com/posts/making-my-blog-readable-by-ai-agents","author":{"name":"Jake Marsh","url":"https://deallocatedobjects.com/about","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Deallocated Objects","url":"https://deallocatedobjects.com/","listing_slug":null,"listing":null},"topics":[{"name":"AI Agents","slug":"ai-agents","url":"https://listedarticles.com/topics/ai-agents"},{"name":"Web Development","slug":"web-development","url":"https://listedarticles.com/topics/web-development"},{"name":"SEO","slug":"seo","url":"https://listedarticles.com/topics/seo"},{"name":"nginx","slug":"nginx","url":"https://listedarticles.com/topics/nginx"},{"name":"Blogging","slug":"blogging","url":"https://listedarticles.com/topics/blogging"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":769,"reading_minutes":3,"published_at":"2026-10-24T13:00:00.000Z","added_at":"2026-10-25T02:14:05.643Z","updated_at":"2026-10-25T02:14:05.643Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/making-my-blog-readable-by-ai-agents","markdown_url":"https://listedarticles.com/articles/making-my-blog-readable-by-ai-agents.md","example":false,"citation":"Jake Marsh, Deallocated Objects. \"Making My Blog Readable By AI Agents.\" 24 Oct 2026. https://deallocatedobjects.com/posts/making-my-blog-readable-by-ai-agents (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://deallocatedobjects.com/posts/making-my-blog-readable-by-ai-agents"},"body_markdown":"A lot of the people reading this blog now aren't people. They're agents: Claude, ChatGPT, Perplexity and friends, fetching a page because somebody asked them a question. This week I spent an evening making Deallocated Objects nice to read for them (and for search engines while I was in there).\n\nThe blog is a static site served by nginx, so everything here is either a file generated at build time or a few lines of nginx config. No framework required.\n\n### Markdown For Every Post\n\nHTML is a lot of noise for an agent to wade through. Every post now also exists as plain markdown. Add `.md` to any post URL:\n\n```\nhttps://deallocatedobjects.com/posts/the-art-of-the-reveal.md\n```\n\nEach file starts with a little YAML frontmatter (title, canonical URL, author, dates, a short description), then the post. The older posts were written in markdown to begin with, so the build just cleans those up and points their images at the self-hosted copies.\n\nThe nicer trick is content negotiation. If a client asks for markdown in its `Accept` header, it gets markdown from the *normal* URL, no `.md` needed. In nginx that's a `map` plus one line per location:\n\n```nginx\n# http context\nmap $http_accept $deallocated_md {\n    default \"\";\n    \"~*text/markdown\" \".md\";\n}\n\n# server block\nlocation ~ ^/posts/[^/.]+$ {\n    if ($deallocated_md) { rewrite ^ $uri.md last; }\n    try_files $uri.html =404;\n}\n```\n\nTry it:\n\n```\ncurl -H \"Accept: text/markdown\" https://deallocatedobjects.com/posts/the-art-of-the-reveal\n```\n\nTwo details matter here. Every response sends `Vary: Accept`, so a cache in the middle never hands HTML to an agent or markdown to a browser. And the `.md` responses send a `Link: <...>; rel=\"canonical\"` header pointing at the HTML page, plus `X-Robots-Tag: noindex`, so Google doesn't treat them as duplicate copies of every post.\n\n### llms.txt\n\n[`/llms.txt`](https://deallocatedobjects.com/llms.txt) is a plain markdown file at the root of the site that tells an agent what it's looking at. Mine has a one-line summary of the blog, how to get markdown, where the feeds are, how I'd like posts cited, and a list of every post with a link to its markdown and a one-line summary.\n\nThere's also [`/llms-full.txt`](https://deallocatedobjects.com/llms-full.txt), which is the full text of every post in one file (about 225 KB), and `/index.md`, which is the homepage as markdown. Every response also carries a `Link` header pointing at `llms.txt`, so an agent can find it without guessing.\n\n### robots.txt\n\nSilence in `robots.txt` technically means \"allowed\", but I'd rather be explicit. Mine allows everyone, then names the big ones explicitly (`GPTBot`, `ClaudeBot`, `PerplexityBot`, `Google-Extended` and about a dozen more) and adds a `Content-Signal` line:\n\n```\nUser-agent: *\nAllow: /\nContent-Signal: search=yes, ai-input=yes, ai-train=yes\n```\n\nThat's the right call for a blog. I *want* to be cited. A product site might choose differently.\n\n### Feeds\n\nThe old Atom feed at `/feed` still works, and every entry now has a summary and a `text/markdown` alternate link. I also added a [JSON Feed](https://deallocatedobjects.com/feed.json), where each item carries a `_markdown_url` (JSON Feed allows custom keys that start with an underscore).\n\n### Meta Tags\n\nThe old site had almost none of these, so every page got the full set:\n\n- a real meta description, a canonical URL, a robots tag and `lang=\"en\"`\n- Open Graph and Twitter card tags, including `article:published_time` and `modified_time`\n- JSON-LD structured data: `BlogPosting` and a breadcrumb on every post, with `WebSite`, `Blog` and a `Person` for me on the index pages\n\nThe descriptions were sneakier than I expected. The old database stored a pre-made description for each post, but it had run the paragraphs together (\"...your app.The RevealWhat do I mean...\") and a few contained raw `<img>` HTML. Now the build makes its summaries from the rendered post text instead.\n\nTwo more fixes from the old 2014 markup. The viewport tag had `maximum-scale=1`, which stops people from pinch-zooming on their phones, so that's gone. And there was no `og:image` at all, so when I shared a post, X and Threads grabbed the first image on the page, which happens to be the Little Bites of Cocoa banner at the top. Every post has a proper 1200x630 preview card now.\n\n### IndexNow\n\nLast, an [IndexNow](https://www.indexnow.org) key and a script that pings Bing and friends whenever a post is published. Google doesn't participate, but Bing's index feeds a lot of AI search tools, so it's worth a 30-line script.\n\nNone of this took long, and most of it is just files. If you have a blog, `llms.txt` and `.md` versions of your posts are a nice place to start. Here's this post:\n\n```\ncurl -H \"Accept: text/markdown\" https://deallocatedobjects.com/posts/making-my-blog-readable-by-ai-agents\n```\n\nThe rest of how this site is built is in the [colophon](https://deallocatedobjects.com/colophon).\n","body_html":"<p>A lot of the people reading this blog now aren&#39;t people. They&#39;re agents: Claude, ChatGPT, Perplexity and friends, fetching a page because somebody asked them a question. This week I spent an evening making Deallocated Objects nice to read for them (and for search engines while I was in there).</p>\n<p>The blog is a static site served by nginx, so everything here is either a file generated at build time or a few lines of nginx config. No framework required.</p>\n<h3 id=\"markdown-for-every-post\">Markdown For Every Post</h3>\n<p>HTML is a lot of noise for an agent to wade through. Every post now also exists as plain markdown. Add <code>.md</code> to any post URL:</p>\n<pre><code>https://deallocatedobjects.com/posts/the-art-of-the-reveal.md</code></pre>\n<p>Each file starts with a little YAML frontmatter (title, canonical URL, author, dates, a short description), then the post. The older posts were written in markdown to begin with, so the build just cleans those up and points their images at the self-hosted copies.</p>\n<p>The nicer trick is content negotiation. If a client asks for markdown in its <code>Accept</code> header, it gets markdown from the <em>normal</em> URL, no <code>.md</code> needed. In nginx that&#39;s a <code>map</code> plus one line per location:</p>\n<pre><code class=\"language-nginx\"># http context\nmap $http_accept $deallocated_md {\n    default &quot;&quot;;\n    &quot;~*text/markdown&quot; &quot;.md&quot;;\n}\n\n# server block\nlocation ~ ^/posts/[^/.]+$ {\n    if ($deallocated_md) { rewrite ^ $uri.md last; }\n    try_files $uri.html =404;\n}</code></pre>\n<p>Try it:</p>\n<pre><code>curl -H &quot;Accept: text/markdown&quot; https://deallocatedobjects.com/posts/the-art-of-the-reveal</code></pre>\n<p>Two details matter here. Every response sends <code>Vary: Accept</code>, so a cache in the middle never hands HTML to an agent or markdown to a browser. And the <code>.md</code> responses send a <code>Link: &lt;...&gt;; rel=&quot;canonical&quot;</code> header pointing at the HTML page, plus <code>X-Robots-Tag: noindex</code>, so Google doesn&#39;t treat them as duplicate copies of every post.</p>\n<h3 id=\"llms-txt\">llms.txt</h3>\n<p><a href=\"https://deallocatedobjects.com/llms.txt\" rel=\"nofollow ugc noopener\"><code>/llms.txt</code></a> is a plain markdown file at the root of the site that tells an agent what it&#39;s looking at. Mine has a one-line summary of the blog, how to get markdown, where the feeds are, how I&#39;d like posts cited, and a list of every post with a link to its markdown and a one-line summary.</p>\n<p>There&#39;s also <a href=\"https://deallocatedobjects.com/llms-full.txt\" rel=\"nofollow ugc noopener\"><code>/llms-full.txt</code></a>, which is the full text of every post in one file (about 225 KB), and <code>/index.md</code>, which is the homepage as markdown. Every response also carries a <code>Link</code> header pointing at <code>llms.txt</code>, so an agent can find it without guessing.</p>\n<h3 id=\"robots-txt\">robots.txt</h3>\n<p>Silence in <code>robots.txt</code> technically means &quot;allowed&quot;, but I&#39;d rather be explicit. Mine allows everyone, then names the big ones explicitly (<code>GPTBot</code>, <code>ClaudeBot</code>, <code>PerplexityBot</code>, <code>Google-Extended</code> and about a dozen more) and adds a <code>Content-Signal</code> line:</p>\n<pre><code>User-agent: *\nAllow: /\nContent-Signal: search=yes, ai-input=yes, ai-train=yes</code></pre>\n<p>That&#39;s the right call for a blog. I <em>want</em> to be cited. A product site might choose differently.</p>\n<h3 id=\"feeds\">Feeds</h3>\n<p>The old Atom feed at <code>/feed</code> still works, and every entry now has a summary and a <code>text/markdown</code> alternate link. I also added a <a href=\"https://deallocatedobjects.com/feed.json\" rel=\"nofollow ugc noopener\">JSON Feed</a>, where each item carries a <code>_markdown_url</code> (JSON Feed allows custom keys that start with an underscore).</p>\n<h3 id=\"meta-tags\">Meta Tags</h3>\n<p>The old site had almost none of these, so every page got the full set:</p>\n<ul><li>a real meta description, a canonical URL, a robots tag and <code>lang=&quot;en&quot;</code></li><li>Open Graph and Twitter card tags, including <code>article:published_time</code> and <code>modified_time</code></li><li>JSON-LD structured data: <code>BlogPosting</code> and a breadcrumb on every post, with <code>WebSite</code>, <code>Blog</code> and a <code>Person</code> for me on the index pages</li></ul>\n<p>The descriptions were sneakier than I expected. The old database stored a pre-made description for each post, but it had run the paragraphs together (&quot;...your app.The RevealWhat do I mean...&quot;) and a few contained raw <code>&lt;img&gt;</code> HTML. Now the build makes its summaries from the rendered post text instead.</p>\n<p>Two more fixes from the old 2014 markup. The viewport tag had <code>maximum-scale=1</code>, which stops people from pinch-zooming on their phones, so that&#39;s gone. And there was no <code>og:image</code> at all, so when I shared a post, X and Threads grabbed the first image on the page, which happens to be the Little Bites of Cocoa banner at the top. Every post has a proper 1200x630 preview card now.</p>\n<h3 id=\"indexnow\">IndexNow</h3>\n<p>Last, an <a href=\"https://www.indexnow.org\" rel=\"nofollow ugc noopener\">IndexNow</a> key and a script that pings Bing and friends whenever a post is published. Google doesn&#39;t participate, but Bing&#39;s index feeds a lot of AI search tools, so it&#39;s worth a 30-line script.</p>\n<p>None of this took long, and most of it is just files. If you have a blog, <code>llms.txt</code> and <code>.md</code> versions of your posts are a nice place to start. Here&#39;s this post:</p>\n<pre><code>curl -H &quot;Accept: text/markdown&quot; https://deallocatedobjects.com/posts/making-my-blog-readable-by-ai-agents</code></pre>\n<p>The rest of how this site is built is in the <a href=\"https://deallocatedobjects.com/colophon\" rel=\"nofollow ugc noopener\">colophon</a>.</p>","headings":[{"level":3,"text":"Markdown For Every Post","id":"markdown-for-every-post"},{"level":3,"text":"llms.txt","id":"llms-txt"},{"level":3,"text":"robots.txt","id":"robots-txt"},{"level":3,"text":"Feeds","id":"feeds"},{"level":3,"text":"Meta Tags","id":"meta-tags"},{"level":3,"text":"IndexNow","id":"indexnow"}]}}