Markdown und MDX sicher mit Claude Code verarbeiten
Markdown/MDX mit Claude Code sicher verarbeiten: AST, Frontmatter, XSS-Schutz, Links und Locale-QA.
Markdown ist kein einfacher Textblock
Ein veröffentlichter Markdown- oder MDX-Artikel besteht nicht nur aus Absätzen. Er enthält Frontmatter, SEO-Description, Überschriftenhierarchie, generierte Anker, Code-Fences, interne Links, offizielle externe Quellen, Sprachrouten und manchmal rohes HTML. Wenn Claude Code nur den Auftrag “verbessere diesen Artikel” bekommt, kann der Text besser klingen und trotzdem den Slug ändern, den CTA entfernen, das Hero-Bild anfassen oder eine Locale als dünne Zusammenfassung zurücklassen.
Der robuste Ansatz trennt Schreiben und Prüfen. Claude Code darf formulieren, lokalisieren und erweitern. Die Struktur muss maschinell überprüfbar sein. Markdown und MDX liest man mit einem AST, also einem abstrakten Syntaxbaum. Frontmatter wird wie strukturierte Daten validiert. HTML-Ausgabe braucht eine klare Sanitization-Grenze. Die zehn Locale-Dateien müssen als ein Set geprüft werden.
Die wichtigsten Quellen wurden am 2. Juni 2026 geprüft. Der unified guide erklärt die Pipeline aus parse, transform und stringify. Die Seite zu syntax trees zeigt, warum ein AST stabiler ist als Zeilen-Regex. Für Markdown sind remark und remark-parse relevant. MDX steht in der MDX-Dokumentation. Frontmatter kann mit gray-matter gelesen werden. Für HTML-Sicherheit vergleiche rehype-sanitize mit dem OWASP XSS Prevention Cheat Sheet. Für Claude-Code-Grenzen helfen Claude Code overview und settings.
flowchart LR
A["MDX-Datei"] --> B["Frontmatter"]
B --> C["Schema-Validierung"]
A --> D["remark / MDX AST"]
D --> E["Headings, Fences, Links"]
D --> F["rehype Pipeline"]
F --> G["Sanitization"]
C --> H["Locale- und Build-Checks"]
E --> H
G --> H
Parser passend zur Aufgabe wählen
Die erste Anweisung an Claude Code sollte die Toolchain nennen. “Parse Markdown” ist zu ungenau. Für kleine Dateien entsteht dann oft eine Regex, die in echten Artikeln falsche Treffer liefert.
| Bedarf | Bessere Wahl | Riskanter Kurzweg |
|---|---|---|
| Überschriften, Links und Code-Fences lesen | remark-parse mit AST Traversal | ^## Regex auf Rohtext |
JSX in .mdx behandeln | remark-mdx oder MDX Compiler | Reiner Markdown Parser |
| HTML rendern | remark-rehype in rehype | HTML-Strings zusammenbauen |
| Rohes HTML erlauben | rehype-raw plus rehype-sanitize | Nur allowDangerousHtml |
| Frontmatter lesen | gray-matter und Schema-Checks | YAML per split lesen |
Ein AST trennt Bedeutung. Ein ## Fake Heading im Codeblock darf nicht im Inhaltsverzeichnis landen. Eine URL in MDX-Props ist nicht immer ein redaktioneller Link. tags: Claude Code, Markdown ist in YAML ein String und kein Array. Solche Fehler sieht ein Parser plus Schema deutlich früher als ein menschlicher Diff-Review.
Vier konkrete Use Cases
Der erste Use Case ist die Aktualisierung eines veröffentlichten Blogartikels. Title, description, updatedDate, offizielle Links, interne Links, Codebeispiele und CTA müssen zusammen passen. Bei ClaudeCodeLab verlinkt man passend auf CLAUDE.md Best Practices und Web Scraping mit Claude Code, ohne andere Slugs zu ändern.
Der zweite Use Case ist eine Dokumentationsseite mit MDX-Komponenten. Callouts, Tabs, Pricing Cards, FAQ und Live-Beispiele sind nützlich, mischen aber Markdown und JSX. Ein Checker ohne MDX-Verständnis wird Komponenten falsch lesen oder wichtige Links übersehen.
Der dritte Use Case ist mehrsprachige Veröffentlichung. Ein starker japanischer Canonical-Artikel reicht nicht, wenn Deutsch, Spanisch, Französisch oder Indonesisch nur kurze Zusammenfassungen sind. Jede Locale braucht Beispiele, Fehlermodi, ausführbare Snippets, offizielle Links, interne Links, CTA und Verifikationsnotiz.
Der vierte Use Case ist kommerzieller Content. Gumroad-Seiten, Training-Seiten, kostenlose Ressourcen und E-Mail-Materialien verwenden oft Markdown. Je näher die Seite an Kauf oder Beratung liegt, desto wichtiger sind überprüfbare Code-Fences, Links und HTML-Regeln.
Minimales Copy-Paste-Setup
Die Beispiele verwenden Node.js 18 oder neuer und ESM. Starte in einem Testordner, bevor du die Skripte ins echte Repository übernimmst.
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
Beispiel 1: Frontmatter, Headings, Fences und Links prüfen
Dieses Skript liest Frontmatter mit gray-matter, parsed den Body mit remark und MDX-Support und meldet fehlende Felder, zu lange description, Code-Fences ohne Sprache sowie fehlende interne oder externe Links.
// 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-de/example.mdx
Beispiel 2: Markdown in sicheres HTML wandeln
Wenn rohes HTML nicht nötig ist, aktiviere es nicht. Wenn es nötig ist, parse es und sanitize direkt danach. allowDangerousHtml allein ist keine Sicherheitsmaßnahme.
// 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));
Die Reihenfolge ist entscheidend. rehype-raw bringt rohes HTML zurück in den HTML-Baum. rehype-sanitize entfernt danach nicht erlaubte Tags und Attribute. Ohne diesen zweiten Schritt kann gefährlicher Inhalt im gerenderten DOM landen.
Beispiel 3: Alle zehn Locales prüfen
Dieses Skript bestätigt, dass derselbe Slug in allen Sprachen existiert, heroImage erhalten bleibt, updatedDate stimmt und jeder Body interne und externe Links enthält.
// 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");
Konkrete Fehlerbilder
| Fehler | Folge | Schutz |
|---|---|---|
| Headings per Regex lesen | Pseudo-Headings aus Codeblöcken landen im TOC | Nur heading Nodes lesen |
tags als String | Filter und Related Posts brechen | Frontmatter-Typen prüfen |
| Unterschiedliche Slugs | Anchor-Links brechen je Sprache | Einheitlichen Slugger nutzen |
| Rohes HTML vertrauen | XSS-Risiko über Tags oder Attribute | Mit Schema sanitizen |
| Externe Links nicht testen | Offizielle Docs ziehen unbemerkt um | Vor Veröffentlichung prüfen |
| Prompt zu breit | Dateien anderer Worker werden geändert | owned_files fixieren |
Diese Fehler gehören in den Prompt. Claude Code arbeitet besser mit überprüfbaren Grenzen als mit subjektiven Sätzen wie “mach es hochwertiger”.
Sicherer Prompt für Claude Code
task: "Refresh one published MDX article"
owned_files:
- "site/src/content/blog-de/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"
Vor dem Publizieren und CTA
Vor Veröffentlichung braucht es lokale Skripte und menschliche Prüfung. Die Skripte prüfen Struktur, Metadaten, Fences, Links und Tiefe. Der Mensch prüft natürliche Sprache, Suchintention, mobile Lesbarkeit und CTA.
node tools/audit-mdx.mjs site/src/content/blog-de/claude-code-markdown-processing.mdx
node tools/check-locales.mjs
node scripts/check-code-fences.mjs
node scripts/check-updated-article-quality.mjs
Einzelpersonen starten mit der kostenlosen Claude Code Cheatsheet. Wer wiederverwendbare Review- und Schreibprompts braucht, nutzt die Claude Code Prompt Templates. Teams, die Berechtigungen, CI, Locale-Workflow und Editorial Review einführen wollen, gehen zu Claude Code Training und Beratung.
Ergebnis der praktischen Prüfung
Für dieses Update behandelte Masa den Artikel als echten Content-Pipeline-Fall: kurze description, updatedDate, erhaltenes heroImage, Sprachen in Code-Fences, offizielle Links, vollständige Locales und CTA. Der AST-basierte Audit deckt Fehler ab, die Regex leicht übersieht, vor allem Headings in Codeblöcken und MDX-Syntax nahe Komponenten. Am Ende wurden node scripts/check-code-fences.mjs und node scripts/check-updated-article-quality.mjs ausgeführt. Die wichtigste Erkenntnis: Claude Code wird zuverlässig, wenn der Artikelvertrag ausführbar ist, nicht nur wenn der Prompt bessere Texte fordert.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Permission Safety Ladder: Zugriff kontrolliert erweitern
Von read-only zu begrenzten Änderungen, Prüfbefehlen und Deploy-Checks mit klarer Kontrolle.
Claude Code Small PR Proof Pack: kleine Änderungen reviewbar machen
Ein Proof Pack für Claude-Code-PRs: Diff, Checks, öffentliche URL, CTA-Pfad und Rollback.
Claude-Code-Review-Gate vor dem Commit
Vor dem Commit mit Claude Code prüfen: Diff, Build, öffentliche URL, Gumroad-Links, Beratung-CTA, fehlende Tests und fremde Dateien.