---
title: "Markdown para Agentes no plano gratuito do Cloudflare Workers · Blog"
description: "Como servir Markdown para agentes de IA no plano gratuito do Cloudflare Workers com a lib open source markdown-edge-for-agents: content negotiation, auto-detecção de crawlers, headers compatíveis com a feature Pro da Cloudflare e redução de 75% de tokens."
source: "https://adhenawer.net/blog/markdown-for-agents-free-tier.html"
lang: pt-BR
---

> **Atualização (abril 2026):** O Worker de regex que este post descrevia originalmente foi extraído, refatorado e publicado como uma biblioteca open source: [markdown-edge-for-agents](https://github.com/adhenawer/markdown-edge-for-agents). Este post foi reescrito para refletir a versão atual, incluindo presets, auto-detecção de crawlers IA e headers compatíveis 1:1 com a feature Pro da Cloudflare.

## O problema

A Cloudflare oferece um recurso chamado *Markdown for Agents* que devolve uma versão limpa em Markdown de qualquer página quando o cliente envia `Accept: text/markdown`. Ele está restrito aos planos **Pro e Business** ($25/mês por zona). Para um blog estático hospedado no GitHub Pages, pagar por um plano de edge só para ajudar crawlers a interpretarem artigos é difícil de justificar.

O problema é real: um artigo deste site pesa cerca de **53.000 tokens** quando um LLM ingere o HTML cru. A maior parte é marcação que o modelo vai descartar (seletor de tema, barra de progresso, banner de retomada, JSON-LD, Open Graph, navegação). Servir o mesmo conteúdo em Markdown com frontmatter YAML reduz para aproximadamente **13.000 tokens** — uma redução de 75%. Isso importa para qualquer pipeline que paga por token ou tem janela de contexto finita.

## De regex a biblioteca

A primeira versão era um Worker de ~170 linhas com regex fazendo a conversão manualmente. Funcionava, mas tinha problemas reais:

- **Acoplado**: os regex conheciam classes CSS específicas do meu site (`.theme-bar`, `.back-home`, `.reading-pct`). Qualquer outro site teria que reescrever.
 - **Frágil**: listas aninhadas, tabelas, blocos de código com language tags — tudo quebrava em edge cases.
 - **Sem testes**: o ciclo de feedback era `curl` no terminal.
 - **Sem reuso**: quem quisesse usar teria que copiar e adaptar 170 linhas.

A solução foi extrair a lógica numa biblioteca publicada no npm. O Worker deste site caiu de 170 linhas para **23 linhas declarativas**:

```typescript
import { createMarkdownWorker } from "@adhenawer-pkg/markdown-edge-for-agents";

export default createMarkdownWorker({
  preset: "custom",
  selector: "article",
  strip: [
    ".theme-bar", ".back-home", ".progress",
    ".reading-pct", ".resume-banner", ".back-top",
    "script", "style", "nav", "button", "header",
  ],
  frontmatter: ["title", "author", "description", "lang"],
  autoDetectAiCrawlers: true,
  redirects: {
    "/leituras/*": "/posts/pt_br/$1",
  },
});
```

Toda a lógica de conversão, negociação de content-type, cache headers, fallback de erros e detecção de crawlers está na lib. O Worker só declara a configuração do site.

## A biblioteca: markdown-edge-for-agents

**[markdown-edge-for-agents](https://github.com/adhenawer/markdown-edge-for-agents)** é uma lib TypeScript + CLI scaffolder para servir Markdown a agentes de IA via content negotiation em Cloudflare Workers. Publicada no npm:

- **Lib:** [@adhenawer-pkg/markdown-edge-for-agents](https://www.npmjs.com/package/@adhenawer-pkg/markdown-edge-for-agents)
 - **CLI:** [create-markdown-edge-for-agents](https://www.npmjs.com/package/create-markdown-edge-for-agents)

### Arquitetura

O projeto é um monorepo com dois pacotes:

- **`packages/core`**: a lib em si. Expõe `createMarkdownWorker(config)` que retorna um `ExportedHandler` pronto para Cloudflare Workers. Internamente usa a lib [markdown-for-agents](https://github.com/kkonstantinov/markdown-for-agents) para conversão HTML→Markdown (htmlparser2, sem JSDOM, sem Turndown) e `HTMLRewriter` nativo do CF para extração de metadados. Em runtimes sem HTMLRewriter (Node, Bun, Deno), cai automaticamente para um fallback regex.
 - **`packages/create`**: scaffolder CLI. Detecta o framework (Astro, Hugo ou custom), gera o Worker + `wrangler.toml` + `package.json` em um comando.

A decisão de usar uma lib externa para a conversão em vez de implementar do zero foi intencional. Converter HTML para Markdown é commodity — não faz sentido reimplementar parsers de listas aninhadas, tabelas, code fences com language detection. O valor da lib está na camada acima: config declarativa, presets por framework, headers compatíveis com a Cloudflare, e auto-detecção de crawlers.

### Presets

Cada framework gera HTML diferente. Em vez de forçar o usuário a descobrir quais seletores CSS remover, a lib traz presets prontos:

| Preset | Selector | Elementos removidos |
| --- | --- | --- |
| `astro` | `article`, `main[data-page-type='post']`, `main.content` | nav, header, footer, aside, script, style, [aria-hidden] |
| `hugo` | `article`, `main .post-content`, `main.single` | nav, header.site-header, footer, .post-nav, .social-share |
| `custom` | `article` | nenhum (você define) |

Adicionar um preset novo é um único arquivo TypeScript com um objeto plano — sem lógica, sem herança.

## Content negotiation

O princípio é simples: mesma URL, conteúdo diferente baseado no que o cliente pede.

- Navegador pede `text/html` → recebe HTML completo (zero processamento)
 - Agente pede `text/markdown` → Worker busca HTML na origem, converte, devolve Markdown
 - Crawler IA detectado pelo User-Agent → recebe Markdown automaticamente (sem depender do header `Accept`)

O header `Vary: Accept` é obrigatório. Sem ele, o cache da Cloudflare pode servir a versão HTML para um agente ou Markdown para um navegador, dependendo de quem pediu primeiro. Com `Vary: Accept`, as duas variantes coexistem no cache sem conflito.

## Auto-detecção de crawlers IA

Aqui está o diferencial real sobre a feature Pro da Cloudflare.

A Cloudflare **depende do crawler enviar `Accept: text/markdown`**. Na prática, quase nenhum faz isso. [Pesquisa do Dries Buytaert](https://dri.es/markdown-llms-txt-and-ai-crawlers) (criador do Drupal) mostrou que em um mês inteiro, **zero requests reais de crawlers IA** usaram o header `Accept: text/markdown`. Todas as 52 requisições que recebeu vieram de ferramentas de auditoria SEO. Os crawlers reais — GPTBot, ClaudeBot, PerplexityBot — simplesmente buscam o HTML como um navegador.

A lib resolve isso com `autoDetectAiCrawlers: true`. Quando ativado, o Worker identifica **16 bots de IA conhecidos** pelo User-Agent e serve Markdown automaticamente:

| Provider | User-Agent patterns |
| --- | --- |
| OpenAI | `GPTBot`, `ChatGPT-User`, `OAI-SearchBot` |
| Anthropic | `ClaudeBot`, `Claude-Web`, `anthropic-ai` |
| Google | `Google-Extended`, `Googlebot-AI` |
| Perplexity | `PerplexityBot` |
| Apple | `Applebot-Extended` |
| Cohere | `cohere-ai` |
| Meta | `Meta-ExternalAgent`, `FacebookExternalHit` |
| Amazon | `Amazonbot` |
| Common Crawl | `CCBot` |
| ByteDance | `Bytespider` |
| Microsoft | `bingbot` |
| You.com | `YouBot` |

A lista é exportada publicamente como `KNOWN_AI_CRAWLERS` para transparência. Qualquer um pode inspecionar ou estender.

## Headers compatíveis com Cloudflare Pro

A resposta Markdown inclui os mesmos headers da feature oficial. Isso tem duas vantagens: agentes que já consomem a API da Cloudflare funcionam sem adaptação, e migrar para o plano Pro no futuro é trivial — basta ligar o toggle e remover o Worker.

| Header | Valor | Propósito |
| --- | --- | --- |
| `Content-Type` | `text/markdown; charset=utf-8` | MIME type |
| `x-markdown-tokens` | `8875` | Estimativa de tokens |
| `x-markdown-version` | `0.2.1` | Versão da lib |
| `x-markdown-preset` | `custom` | Preset ativo |
| `Content-Signal` | `ai-train=yes, search=yes, ai-input=yes` | Sinal de opt-in para IA |
| `Vary` | `Accept` | Cache diferencia HTML e Markdown |
| `Cache-Control` | `public, max-age=3600, stale-while-revalidate=86400` | Configurável |

## Comparação com a feature Pro

| Recurso | Cloudflare Pro ($25/mês) | markdown-edge-for-agents |
| --- | --- | --- |
| Content negotiation via `Accept` | Sim | Sim |
| Auto-detecção de crawlers IA por User-Agent | **Não** | **Sim (16 bots)** |
| `x-markdown-tokens` | Sim | Sim |
| `Content-Signal` | Sim | Sim |
| `Vary: Accept` | Sim | Sim |
| Presets por framework | Não | Sim (Astro, Hugo, custom) |
| Customização de strip/selector | Limitada | Total |
| Custo | $25/zona/mês | Grátis (100k req/dia) |

## Tratamento de erros

O Worker nunca deve deixar o agente sem resposta. A lib implementa fallback em cascata:

- **Origem retorna 404 ou 5xx**: passa a resposta original intacta.
 - **HTML não tem `<article>`** (selector não bateu): retorna 404 com mensagem clara.
 - **Conversão explode** por bug inesperado: devolve o HTML original com um header `x-markdown-error` descrevendo o problema. O agente continua funcionando (com mais tokens), e o owner vê o erro nos logs.
 - **Payload grande demais** (>10 MB): retorna 413 antes de ler o body completo.
 - **Content-Type não é HTML**: passa adiante sem tocar.

## Runtime agnóstico

A lib auto-detecta o runtime. Em Cloudflare Workers, usa `HTMLRewriter` nativo (streaming, sem deps extras) para extrair metadados e limpar HTML antes da conversão. Em Node, Bun, Deno ou browser, cai automaticamente para um fallback regex que cobre seletores de tag (`nav`, `script`), classe (`.theme-bar`) e combinações (`div.ad`).

Isso permite testar localmente com `node` direto, sem precisar de `wrangler dev`:

```typescript
import { convertHtmlToMarkdown } from "@adhenawer-pkg/markdown-edge-for-agents";

const result = await convertHtmlToMarkdown(html, {
  selector: "article",
  strip: ["nav", "script"],
  frontmatter: ["title", "author"],
});

console.log(result?.markdown);
console.log(result?.tokens);
console.log(result?.meta);
```

## Scaffolder para novos projetos

Pra quem quer usar em outro site sem configurar manualmente, o CLI detecta o framework e gera tudo:

```bash
npx create-markdown-edge-for-agents init
```

O scaffolder identifica Astro (via `package.json`), Hugo (via `hugo.toml`/`hugo.yaml`) ou custom, pergunta zona e pattern de rotas, e cria:

- `workers/markdown-agent/src/index.ts` — Worker pronto (5 linhas com preset)
 - `workers/markdown-agent/wrangler.toml` — configurado com seu domínio
 - `workers/markdown-agent/package.json` — com a lib como dependência

Depois é `npm install && npx wrangler deploy`.

## Custo e limites

O plano gratuito dos Workers oferece 100.000 requisições por dia e 10 ms de CPU por invocação. O bundle total da lib (com `markdown-for-agents` + `zod`) gera um Worker de ~94 KB gzipped. A conversão de um artigo de 50 KB roda em poucos milissegundos. Um blog pequeno nunca chega perto de nenhum dos tetos.

O projeto tem 62 testes automatizados, CI rodando em GitHub Actions, typecheck com TypeScript strict, lint com Biome, e coverage threshold de 85%. A versão original com regex não tinha nada disso.

## Observabilidade

O Worker suporta a observabilidade nativa da Cloudflare. No `wrangler.toml`:

```toml
[observability]
enabled = true

[observability.logs]
enabled = true
head_sampling_rate = 1
invocation_logs = true

[observability.traces]
enabled = true
head_sampling_rate = 1
```

Logs e traces aparecem no dashboard da Cloudflare em tempo real. Com `debug: true` na config do Worker, headers extras são adicionados à resposta (`x-markdown-debug-bytes-in`, `x-markdown-debug-bytes-out`, `x-markdown-debug-selector`) para debugging sem precisar abrir o dashboard.

## Experimente

```bash
# Navegador recebe HTML (comportamento normal)
curl -sI https://adhenawer.net/posts/original/head-claude-code-happens-after-coding-solved.html \
  | grep -i content-type
# content-type: text/html; charset=utf-8

# Agente com Accept header recebe Markdown
curl -s https://adhenawer.net/posts/original/head-claude-code-happens-after-coding-solved.html \
  -H "Accept: text/markdown" | head -15
# ---
# title: "The Head of Claude Code on What Happens After Coding Is Solved"
# author: "Lenny's Podcast with Boris Cherny (Head of Claude Code, Anthropic)"
# description: "100% of my code is written by Claude Code..."
# source: "https://adhenawer.net/posts/original/head-claude-code-happens-after-coding-solved.html"
# lang: en
# ---
# ## THE PROGRAMMER WHO NO LONGER WRITES CODE

# GPTBot recebe Markdown automaticamente (sem header Accept!)
curl -s -A "GPTBot/1.0" \
  https://adhenawer.net/posts/original/head-claude-code-happens-after-coding-solved.html \
  -o /dev/null -w "content-type: %{content_type}\n"
# content-type: text/markdown; charset=utf-8

# Headers CF-compat
curl -sI -H "Accept: text/markdown" \
  https://adhenawer.net/posts/original/head-claude-code-happens-after-coding-solved.html \
  | grep -iE "x-markdown|content-signal|vary"
# content-signal: ai-train=yes, search=yes, ai-input=yes
# vary: Accept
# x-markdown-preset: custom
# x-markdown-tokens: 8875
# x-markdown-version: 0.2.1
```

Mesma URL, `Accept` diferente (ou crawler detectado), uma fração dos tokens. Nenhum upgrade de plano necessário.

## Links

- **GitHub:** [github.com/adhenawer/markdown-edge-for-agents](https://github.com/adhenawer/markdown-edge-for-agents)
 - **npm (lib):** [@adhenawer-pkg/markdown-edge-for-agents](https://www.npmjs.com/package/@adhenawer-pkg/markdown-edge-for-agents)
 - **npm (CLI):** [create-markdown-edge-for-agents](https://www.npmjs.com/package/create-markdown-edge-for-agents)
 - **Worker deste site:** [video-to-text/workers/markdown-agent/src/index.ts](https://github.com/adhenawer/video-to-text/blob/main/workers/markdown-agent/src/index.ts)
 - **Upstream (conversor):** [markdown-for-agents](https://github.com/kkonstantinov/markdown-for-agents)