Processamento seguro de Markdown e MDX com Claude Code
Processe Markdown/MDX com Claude Code: AST, frontmatter, XSS, links e QA de locales.
Markdown não é apenas texto
Um artigo publicado em Markdown ou MDX não é só um bloco de parágrafos. Ele carrega frontmatter, description para SEO, hierarquia de títulos, âncoras geradas, blocos de código, links internos, fontes oficiais, rotas por idioma e às vezes HTML cru. Se você pedir ao Claude Code “melhore este artigo” sem um contrato técnico, o texto pode ficar melhor, mas o slug pode mudar, o CTA pode sumir, a imagem hero pode ser alterada ou uma locale pode virar um resumo fino.
A regra prática é separar escrita e verificação. Claude Code pode reescrever, localizar e expandir. A estrutura precisa ser checada por scripts. Markdown e MDX devem ser lidos com AST, ou árvore de sintaxe abstrata. Frontmatter deve ser validado como dado. HTML precisa de uma fronteira explícita de sanitização. E as dez locales precisam ser vistas como um conjunto.
As fontes principais foram verificadas em 2 de junho de 2026. O guia de unified explica o pipeline de parse, transform e stringify. A página de syntax trees mostra por que AST é mais seguro que regex de linha. Para Markdown, use remark e remark-parse. Para MDX, veja a documentação oficial do MDX. Para frontmatter, gray-matter é direto. Para HTML e XSS, compare rehype-sanitize com o OWASP XSS Prevention Cheat Sheet. Para limitar o agente, leia Claude Code overview e settings.
flowchart LR
A["Arquivo MDX"] --> B["frontmatter"]
B --> C["schema validation"]
A --> D["remark / MDX AST"]
D --> E["títulos, fences, links"]
D --> F["pipeline rehype"]
F --> G["sanitize"]
C --> H["locale e build checks"]
E --> H
G --> H
Escolha o parser antes de editar
A primeira instrução ao Claude Code deve nomear a toolchain. “Parse Markdown” é amplo demais. Para um arquivo pequeno, uma regex parece boa; para conteúdo publicado, ela falha em casos comuns.
| Necessidade | Melhor escolha | Atalho arriscado |
|---|---|---|
| Ler títulos, links e code fences | remark-parse com AST traversal | Regex ^## no texto |
Suportar JSX em .mdx | remark-mdx ou compiler MDX | Parser só de Markdown |
| Gerar HTML | remark-rehype para rehype | Concatenar strings HTML |
| Permitir raw HTML | rehype-raw e rehype-sanitize | Só allowDangerousHtml |
| Ler frontmatter | gray-matter com schema checks | Split manual de YAML |
AST separa significado. Um ## título falso dentro de bloco de código não pode entrar no índice. Uma URL dentro de props MDX pode não ser um link editorial. tags: Claude Code, Markdown é string, não array. Parser e schema pegam esse tipo de erro antes da revisão humana.
Quatro casos de uso práticos
O primeiro caso é atualizar um artigo já publicado. Title, description, updatedDate, links oficiais, links internos, exemplos de código e CTA precisam caminhar juntos. No ClaudeCodeLab, isso se conecta a boas práticas de CLAUDE.md e web scraping com Claude Code, sem tocar em outros slugs.
O segundo caso é documentação com componentes MDX. Callouts, abas, cards de preço, FAQ e exemplos vivos são úteis, mas misturam Markdown e JSX. Um checker que não entende MDX pode quebrar componentes ou ignorar links relevantes.
O terceiro caso é publicação multilíngue. Um canonical japonês forte não resolve se português, espanhol, francês ou indonésio viram resumos. Cada locale precisa ter exemplos, armadilhas, snippets executáveis, links oficiais, links internos, CTA e nota de verificação.
O quarto caso é operação comercial de conteúdo. Páginas de Gumroad, training, recursos gratuitos e emails frequentemente reaproveitam Markdown. Quanto mais perto de compra ou consultoria, mais importante é verificar code fences, links e HTML.
Setup mínimo para copiar
Os exemplos usam Node.js 18 ou superior e ESM. Comece em uma pasta de teste antes de levar para o repositório.
mkdir mdx-audit-demo
cd mdx-audit-demo
npm init -y
npm pkg set type=module
npm install unified remark-parse remark-mdx remark-gfm gray-matter
npm install unist-util-visit github-slugger
npm install remark-rehype rehype-raw rehype-sanitize rehype-stringify
mkdir tools
Exemplo 1: auditar frontmatter, títulos, fences e links
Este script lê frontmatter com gray-matter, faz parse do body com remark e suporte MDX, e falha se campos obrigatórios faltarem, se description passar de 120 caracteres, se um code fence não tiver linguagem ou se faltarem links internos e externos.
// tools/audit-mdx.mjs
import fs from "node:fs/promises";
import matter from "gray-matter";
import GithubSlugger from "github-slugger";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkMdx from "remark-mdx";
import remarkGfm from "remark-gfm";
import { visit } from "unist-util-visit";
const file = process.argv[2];
if (!file) {
throw new Error("Usage: node tools/audit-mdx.mjs article.mdx");
}
const source = await fs.readFile(file, "utf8");
const { data, content } = matter(source);
const errors = [];
const links = { internal: [], external: [] };
const headings = [];
const codeBlocks = [];
for (const key of ["title", "description", "pubDate", "heroImage", "lang"]) {
if (typeof data[key] !== "string" || data[key].trim() === "") {
errors.push(`frontmatter.${key} is required`);
}
}
if ([...String(data.description ?? "")].length > 120) {
errors.push("description must be 120 characters or fewer");
}
if (!Array.isArray(data.tags) || data.tags.length === 0) {
errors.push("frontmatter.tags must be a non-empty array");
}
const tree = unified()
.use(remarkParse)
.use(remarkMdx)
.use(remarkGfm)
.parse(content);
const slugger = new GithubSlugger();
visit(tree, (node) => {
if (node.type === "heading") {
const text = plainText(node);
headings.push({ depth: node.depth, text, slug: slugger.slug(text) });
}
if (node.type === "code") {
codeBlocks.push({ lang: node.lang || "", meta: node.meta || "" });
if (!node.lang) errors.push("code fence is missing a language");
}
if (node.type === "link") {
const url = String(node.url || "");
if (url.startsWith("http")) links.external.push(url);
if (url.startsWith("/")) links.internal.push(url);
}
});
if (links.internal.length === 0) errors.push("missing internal link");
if (links.external.length === 0) errors.push("missing external link");
if (errors.length > 0) {
console.error(errors.map((error) => `- ${error}`).join("\n"));
process.exit(1);
}
console.log(JSON.stringify({ headings, codeBlocks, links }, null, 2));
function plainText(node) {
if (typeof node.value === "string") return node.value;
if (!Array.isArray(node.children)) return "";
return node.children.map(plainText).join("");
}
node tools/audit-mdx.mjs site/src/content/blog-pt/example.mdx
Exemplo 2: converter Markdown para HTML seguro
Se você não precisa de raw HTML, não habilite. Se precisa, faça parse e sanitize logo depois. allowDangerousHtml sozinho não é defesa.
// tools/markdown-to-safe-html.mjs
import fs from "node:fs/promises";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkGfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypeRaw from "rehype-raw";
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
import rehypeStringify from "rehype-stringify";
const file = process.argv[2];
const markdown = await fs.readFile(file, "utf8");
const schema = {
...defaultSchema,
attributes: {
...defaultSchema.attributes,
code: [["className", /^language-/]],
},
};
const html = await unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw)
.use(rehypeSanitize, schema)
.use(rehypeStringify)
.process(markdown);
console.log(String(html));
A ordem importa. rehype-raw devolve HTML cru para a árvore HTML; rehype-sanitize remove tags e atributos não permitidos. Sem a segunda etapa, conteúdo perigoso pode chegar ao DOM renderizado.
Exemplo 3: verificar as dez locales
Este script confirma que o mesmo slug existe em todos os idiomas, que heroImage foi preservado, que updatedDate está correto e que cada body contém link interno e externo.
// tools/check-locales.mjs
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";
const slug = "claude-code-markdown-processing.mdx";
const expectedHero = "/images/hero/hero-077.png";
const locales = [
["ja", "site/src/content/blog"],
["en", "site/src/content/blog-en"],
["zh", "site/src/content/blog-zh"],
["ko", "site/src/content/blog-ko"],
["es", "site/src/content/blog-es"],
["fr", "site/src/content/blog-fr"],
["de", "site/src/content/blog-de"],
["pt", "site/src/content/blog-pt"],
["hi", "site/src/content/blog-hi"],
["id", "site/src/content/blog-id"],
];
const errors = [];
for (const [lang, dir] of locales) {
const file = path.join(dir, slug);
const source = fs.readFileSync(file, "utf8");
const { data, content } = matter(source);
if (data.lang !== lang) errors.push(`${lang}: lang mismatch`);
if (data.heroImage !== expectedHero) errors.push(`${lang}: hero changed`);
if (data.updatedDate !== "2026-06-02") {
errors.push(`${lang}: updatedDate mismatch`);
}
if ([...String(data.description ?? "")].length > 120) {
errors.push(`${lang}: description too long`);
}
if (!content.includes("https://")) errors.push(`${lang}: no external link`);
if (!content.includes("](/")) errors.push(`${lang}: no internal link`);
}
if (errors.length > 0) {
console.error(errors.map((error) => `- ${error}`).join("\n"));
process.exit(1);
}
console.log("locale set is consistent");
Falhas concretas
| Falha | Resultado | Proteção |
|---|---|---|
| Ler títulos com regex | Falso título dentro de código entra no índice | Percorrer nós heading |
tags vira string | Filtros e posts relacionados quebram | Validar tipos do frontmatter |
| Slug inconsistente | Âncoras quebram por idioma | Usar o mesmo slugger |
| Confiar em raw HTML | Risco XSS por tags ou atributos | Sanitizar com schema |
| Não checar links externos | Docs oficiais mudam sem alerta | Testar antes de publicar |
| Prompt amplo demais | Arquivos de outros workers são alterados | Fixar owned_files |
Essas falhas devem estar no prompt. Claude Code responde melhor a restrições verificáveis do que a uma frase vaga como “deixe mais profissional”.
Prompt seguro para Claude Code
task: "Refresh one published MDX article"
owned_files:
- "site/src/content/blog-pt/claude-code-markdown-processing.mdx"
preserve:
- "slug path"
- "heroImage"
- "unrelated dirty files"
required:
- "updatedDate: 2026-06-02"
- "description <= 120 characters"
- "AST-based Markdown checks"
- "official external links"
- "internal links and monetization CTA"
forbidden:
- "regex-only heading parsing"
- "raw HTML without sanitization"
- "thin locale summaries"
verification:
- "node scripts/check-code-fences.mjs"
- "node scripts/check-updated-article-quality.mjs"
Checagem antes de publicar e CTA
Antes de publicar, combine scripts e revisão humana. Scripts checam estrutura, metadata, fences, links e profundidade. A pessoa revisa naturalidade, intenção de busca, leitura mobile e CTA.
node tools/audit-mdx.mjs site/src/content/blog-pt/claude-code-markdown-processing.mdx
node tools/check-locales.mjs
node scripts/check-code-fences.mjs
node scripts/check-updated-article-quality.mjs
Para uso individual, comece pela cheatsheet gratuita de Claude Code. Para prompts reutilizáveis de review e escrita, use os Claude Code prompt templates. Para equipes que precisam de permissões, CI, fluxo multilíngue e revisão editorial, veja treinamento e consultoria Claude Code.
Resultado do teste prático
Nesta atualização, Masa tratou o artigo como pipeline real de conteúdo: description curta, updatedDate, heroImage preservado, linguagens em code fences, links oficiais, locales completos e CTA. A auditoria baseada em AST cobre erros que regex não vê, principalmente títulos dentro de blocos de código e sintaxe MDX perto de componentes. No final, os comandos usados foram node scripts/check-code-fences.mjs e node scripts/check-updated-article-quality.mjs. A lição é que Claude Code fica confiável quando o contrato do artigo é executável, não apenas quando o prompt pede “mais qualidade”.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Como pedir ao Claude Code para mexer em um único arquivo
Do desastre em que um 'deixa melhor' alterou 40 linhas nasceu um template de prompt que limita o escopo, valida e permite reverter.
Recuperar de negações de permissão no Claude Code sem enfraquecer guardrails
Transforme um comando negado em plano seguro com motivo, alternativa, provas e critérios de nova tentativa.
Claude Code Harness Smoke Test: prova de 15 minutos antes de confiar em um agente
Um smoke test para escopo, áreas bloqueadas, comandos de prova, URL pública e CTAs de receita no Claude Code.