Quick start

The smallest working setup.

One URL. Two representations. One correct Vary header. Every recipe on this site is a variation of the three steps below.

1. Detect the Accept header

When a request comes in, read Accept. If it asks for text/markdown before text/html, you'll serve Markdown. Otherwise, you'll serve HTML.

Don't substring-match. Parse the header, sort by q-value, break ties by specificity. Or use a library — every stack has one. See the Accept parsing guide for the gotchas.

2. Respond with the matching representation

You need two things in the response:

If you can't satisfy the request at all (the client specifically asked for something you don't do), return 406 Not Acceptable instead of silently falling back.

3. Verify with curl

curl -sI -H "Accept: text/markdown" https://yoursite.com/article

You should see Content-Type: text/markdown and Vary: Accept. Swap the Accept header to text/html and you should get HTML back from the same URL.

Or use the probe on the home page to check any URL.


What's next

Pick your stack: Nginx, Caddy, WordPress, Laravel, Rails, Cloudflare Workers, Next.js, Astro, Apache, SvelteKit, Nuxt/Nitro, Express, Django. Each recipe is the three steps above translated into that stack's idioms, usually in under 20 lines of config or code.

If your site sits behind Cloudflare, you may not need to do any of this yourself — see Markdown for Agents.