Write in Markdown, serve it dynamic

Leer en español

We have three kinds of visitors: one is a primate and the other two are robots. Three very different readers, with very different needs, and almost everyone serves all of them exactly the same thing. That's why, on my blog, I dynamically serve the same content in three different formats, adapted to each way of consuming it.

Before you keep reading, I want you to know that this could be seen as an extended explanation of one of the points in my other article The Dark Side of Static Sites.

One article, three readers

Think about who reaches this page:

  • Humans: They want a rich experience. Pleasant designs with good contrast, features like infinite pagination, comments, smooth navigation without reloads. In other words: design, content, and experience. Everything that makes the web a pleasant place to be.
  • Indexers: The bots from Google, Bing, or DuckDuckGo. They want clean, stable HTML, without depending on JavaScript running. They only read your content and classify it by the tag structure. They don't care about fireworks, dropdown menus, or sliders. They only want the content, and they want it fast.
  • AIs and agents: Claude, ChatGPT, Perplexity, and company. Like the indexers, they don't want your layout, your menu, or your footer. But not the structure, hierarchy, or content tags either (<section>, <article>, <h1>, <h2>). They want your raw text, and cheap, spending the fewest tokens possible.

But all three reach the same URL.

The same content, negotiated

The solution is not to have three websites, but a single source of content served in the format each visitor asks for. And it turns out HTTP already had the answer decades ago: the Accept header.

Each request to the server states which format it prefers. A browser asks for text/html. An agent can ask for text/markdown. The server looks at that header and responds accordingly. It's called content negotiation, and it has been in the HTTP standard for almost thirty years. The same mechanism that serves HTML to a browser, JSON to an app, or RSS to a feed reader. The strange thing is that, until recently, almost no one used it to serve Markdown to AIs.

In my case, anyone can check it. Ask for this same article in Markdown:

curl -H "Accept: text/markdown" https://en.andros.dev/blog/...

And if you want HTML, ask:

curl -H "Accept: text/html" https://en.andros.dev/blog/...

The same URL. The same content. A different format.

flowchart TD
    A[Request to the same URL] --> B{What does the Accept header say?}
    B -->|text/markdown| C[AI / Agent: pure Markdown, minimal tokens]
    B -->|text/html, indexer bot| D[Indexer: static HTML, no JS execution]
    B -->|text/html, browser| E[Human: HTML over WebSockets, dynamic SPA]
    C --> F[Same content, a single source]
    D --> F
    E --> F

Maybe you have heard of llms.txt, the file that lists your content in Markdown for AIs. It's complementary, not the same thing: llms.txt is for being discovered, content negotiation is for serving each page once they have found you. I'll go with the second one, because it doesn't force me to maintain a separate index to keep in sync.

Why you give Markdown to AIs

It's not an aesthetic whim. It's economics.

A language model pays per token. Every HTML tag, every CSS class, every nested <div> are tokens it consumes to reach your text. Put a number on it: my article The Dark Side of Static Sites weighs around 36 KB in HTML and around 8 KB in Markdown. More than four times smaller, a 76% reduction, and that's with my page already being fairly light. On heavier sites the difference reaches 80% to 99%. Fewer tokens means less cost, less noise, and a higher chance the model understands what you say.

Think about it from the other side. If your content is easy for an AI to read, it's more likely to cite you correctly when someone asks. In a world where more and more people ask an assistant instead of a search engine, that is the new optimization. The SEO of the next decade might just be called serving good Markdown.

Isn't this cheating?

It's a fair question. If you serve something different to bots than to humans, isn't that cloaking, the technique Google penalizes?

No, and the difference matters. Cloaking means serving different content at the same URL to deceive: one thing to the search engine to rank, a different one to the human. Here the content is the same. The only thing that changes is the presentation format, and it changes because the client itself asks for it with its Accept header. There's no deception. There's courtesy. The article says exactly the same thing in all three versions.

The rule is simple: same facts, same structure, same links and dates. You only remove the wrapper that isn't useful to that visitor.

Humans, without giving up anything

For people I do exactly the opposite of what I do for AIs: I give them the richest experience possible. The site behaves like a SPA with HTML over WebSockets: you navigate without reloads, the search responds instantly, navigation is fast.

However, if you don't have JavaScript enabled, the site still works. The content is served as static HTML, with links and pagination. There's nothing you can't read, nothing you can't navigate. You only lose the enriched experience.

Why dynamic makes it easy

You might think all of this can be done with a static generator. And in part that's true. You can generate a duplicate .md for each page alongside its HTML. In fact, some do it with Eleventy. Even Cloudflare and Vercel already offer it, converting your HTML to Markdown on the fly at the edge.

But notice the nuance. Cloudflare starts from your HTML and reconverts it to Markdown: it starts from the already-wrapped version to undo the wrapper. A static generator duplicates a .md on every build and trusts it not to fall out of sync. I don't reconvert anything or duplicate anything: I serve the original source in Markdown, which is exactly what I wrote.

The difference is where the complexity lives. On a static site, each new format is one more artifact to generate, version, and keep in sync. On a dynamic backend, the content lives just once in Markdown and the format is decided at request time. No copies to fall out of sync. No build to remember. You look at the Accept header and respond. Adding a fourth format tomorrow is a function, not a pipeline.

That, to me, is the advantage of a dynamic site that isn't obvious at first glance. It's not having comments or a search box. It's being able to treat each visitor as what they are, instead of serving the same package to everyone and hoping it works for them.

Conclusion

And here is the idea I want you to take away.

When someone defends static sites, the star argument is usually the same: I write in plain text, in Markdown, with my editor and my git repository. And it's a great argument. I write that way too. I'm not giving it up. But notice the trap. "I write in Markdown" and "I use a static generator" have fused in everyone's head as if they were the same decision. They're not. One thing is how you write. Another very different thing is how you serve what you write. You can keep the first and change the second.

A static generator takes your Markdown, compiles it to HTML at build time, and, in practice, throws the Markdown in the trash. What it serves is HTML. If you later want to give Markdown to an AI, you have to generate it again separately, as a copy. The Markdown stopped being the source the moment of the build.

A dynamic site does the opposite. The Markdown stays alive. It's the source, not an ingredient consumed at compile time. Each request renders it to the right format: HTML for the human, plain HTML for the indexer, Markdown as-is for the AI. Serving the original costs nothing, because the original never left.

So now you know: write in Markdown, but don't serve it with a static generator, serve it from a dynamic backend.

Sources

This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.

Will you buy me a coffee?

This is how I keep writing without ads or paywalls.

Comments

There are no comments yet.

You may also like

Visitors in real time

You are alone: