vibecheck0.3.0
AI readinessaeomarkdown-negotiation-missinginfo

No markdown content negotiation in Vanilla JS

When an agent requests your page with Accept: text/markdown and gets HTML back, it has to parse the full DOM — navigation, scripts, styling — to recover the content. Offering a markdown representation of the same URL hands agents clean, structured text directly. It’s an advanced, optional signal, but a strong one for agent-first sites and docs.

Symptoms

  • Requesting the page with Accept: text/markdown returns HTML
  • Agents parse noisy DOM instead of clean content
  • No lightweight text representation for programmatic consumers

How VibeCheck catches it

In your widget · Problems

infoaeoNo markdown content negotiation

To your coding agent · MCP

agent › get_detected_issues
{ detector: "aeo", issue: "No markdown content negotiation", threshold: "A request with Accept: text/markdown still returns an HTML content type" }

The same string in your widget and in your agent’s context — no screenshot, no copy-paste.

Root causes

  • The server ignores the Accept header and always returns HTML
  • No markdown source or renderer exposed for content routes

The fix for Vanilla JS

In any server, check the Accept header on content routes and serve the markdown source when it’s preferred.

js
app.get('/docs/:slug', (req, res) => {
  const wantsMd = (req.get('Accept') || '').includes('text/markdown')
  res.vary('Accept')
  res.type(wantsMd ? 'text/markdown' : 'text/html').send(wantsMd ? doc.md : doc.html)
})

Steps

  1. Detect Accept: text/markdown on your content routes
  2. Return the markdown representation with a text/markdown content type
  3. Send Vary: Accept so caches don’t mix HTML and markdown responses

See the general, framework-agnostic fix →

FAQ

Is markdown negotiation required?
No — it’s an optional, advanced AEO signal, most valuable for docs and agent-facing sites. VibeCheck reports it as info, not an error. Skip it if your content isn’t markdown-backed.
Why send Vary: Accept?
So shared caches and CDNs store the HTML and markdown responses separately per Accept header, instead of serving one to a client that asked for the other.
How is this different from llms.txt?
llms.txt is one static index of your key content for LLMs; markdown negotiation serves a clean markdown version of any URL on request. They complement each other — llms.txt points agents at the content, negotiation delivers it without the DOM noise.