Tips & Tricks (Diperbarui: 2/6/2026)

Memproses Markdown dan MDX dengan Aman memakai Claude Code

Proses Markdown/MDX dengan Claude Code: AST, frontmatter, XSS, link, dan QA locale.

Memproses Markdown dan MDX dengan Aman memakai Claude Code

Markdown bukan sekadar teks

Artikel Markdown atau MDX yang sudah publish bukan hanya kumpulan paragraf. Di dalamnya ada frontmatter, SEO description, struktur heading, anchor yang dihasilkan otomatis, code fence, internal link, referensi resmi, route per bahasa, dan kadang raw HTML. Jika Anda meminta Claude Code “rapikan artikel ini” tanpa kontrak teknis, tulisannya bisa lebih enak dibaca tetapi slug berubah, CTA hilang, hero image tersentuh, atau salah satu locale menjadi ringkasan tipis.

Pendekatan yang aman adalah memisahkan penulisan dan verifikasi. Claude Code boleh menulis ulang, melokalkan, dan memperluas konten. Struktur harus diperiksa dengan script. Markdown dan MDX sebaiknya dibaca sebagai AST, yaitu abstract syntax tree. Frontmatter divalidasi seperti data. HTML harus melewati batas sanitization yang jelas. Semua file locale diperiksa sebagai satu paket.

Referensi utama sudah saya verifikasi pada 2 Juni 2026. unified guide menjelaskan pipeline parse, transform, dan stringify. Panduan syntax trees menjelaskan mengapa AST lebih aman daripada membaca baris mentah. Untuk Markdown, lihat remark dan remark-parse. Untuk MDX, rujuk dokumentasi MDX. Untuk frontmatter, gunakan gray-matter. Untuk HTML dan XSS, bandingkan rehype-sanitize dengan OWASP XSS Prevention Cheat Sheet. Untuk batas kerja agent, baca Claude Code overview dan settings.

flowchart LR
  A["File MDX"] --> B["frontmatter"]
  B --> C["schema validation"]
  A --> D["remark / MDX AST"]
  D --> E["heading, fence, link"]
  D --> F["pipeline rehype"]
  F --> G["sanitize"]
  C --> H["locale dan build checks"]
  E --> H
  G --> H

Pilih parser sebelum Claude Code mengedit

Instruksi pertama untuk Claude Code harus menyebut toolchain. “Parse Markdown” terlalu luas. Untuk contoh kecil, regex terlihat cukup. Untuk artikel produksi, regex sering membaca heading palsu di dalam code block atau melewatkan sintaks MDX.

KebutuhanPilihan lebih amanShortcut berisiko
Membaca heading, link, code fenceremark-parse dengan AST traversalRegex ^## pada teks
Mendukung JSX di .mdxremark-mdx atau compiler MDXParser Markdown saja
Menghasilkan HTMLremark-rehype ke rehype pipelineMenyambung string HTML
Mengizinkan raw HTMLrehype-raw lalu rehype-sanitizeHanya allowDangerousHtml
Membaca frontmattergray-matter dan schema checksSplit YAML manual

AST memisahkan makna. ## heading palsu di dalam code fence tidak boleh masuk daftar isi. URL di props komponen MDX belum tentu link editorial. tags: Claude Code, Markdown dalam YAML adalah string, bukan array. Parser dan schema validation menangkap masalah ini lebih cepat daripada review manual.

Empat use case praktis

Use case pertama adalah refresh artikel yang sudah publish. Title, description, updatedDate, link resmi, internal link, contoh kode, dan CTA harus diperbarui bersama. Di ClaudeCodeLab, alur ini bisa tersambung ke best practices CLAUDE.md dan web scraping dengan Claude Code, tetapi tidak boleh menyentuh slug lain.

Use case kedua adalah dokumentasi dengan komponen MDX. Callout, tab, pricing card, FAQ, dan contoh live berguna, tetapi mencampur Markdown dan JSX. Checker yang tidak memahami MDX mudah merusak komponen atau melewatkan link penting.

Use case ketiga adalah publishing multibahasa. Canonical Jepang yang kuat tidak cukup jika bahasa Inggris, Spanyol, Prancis, Hindi, atau Indonesia hanya ringkasan. Setiap locale perlu contoh konkret, failure mode, snippet yang bisa dijalankan, link resmi, internal link, CTA, dan catatan verifikasi.

Use case keempat adalah operasi konten komersial. Halaman Gumroad, training, resource gratis, dan email sering memakai Markdown. Semakin dekat konten ke pembelian atau konsultasi, semakin penting code fence, link, dan keamanan HTML.

Setup minimum yang bisa disalin

Contoh berikut memakai Node.js 18 atau lebih baru dan ESM. Jalankan di folder demo dulu sebelum dipindah ke repo produksi.

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

Script ini membaca frontmatter dengan gray-matter, mem-parse body dengan remark dan dukungan MDX, lalu gagal jika field wajib kosong, description lebih dari 120 karakter, code fence tidak punya bahasa, atau tidak ada internal dan external link.

// 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-id/example.mdx

Contoh 2: mengubah Markdown menjadi HTML aman

Jika raw HTML tidak diperlukan, jangan aktifkan. Jika diperlukan, parse lalu sanitize langsung. allowDangerousHtml saja bukan strategi keamanan.

// 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));

Urutannya penting. rehype-raw mengembalikan raw HTML ke HTML tree. rehype-sanitize menghapus tag dan atribut yang tidak diizinkan. Tanpa langkah kedua, konten berisiko bisa sampai ke DOM.

Contoh 3: mengecek sepuluh file locale

Script ini memastikan slug yang sama ada di semua bahasa, heroImage tetap sama, updatedDate benar, description tidak terlalu panjang, dan body punya link internal serta eksternal.

// 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");

Failure mode yang perlu disebutkan

FailureDampakGuardrail
Heading dibaca dengan regexHeading palsu di code block masuk TOCBaca node heading saja
tags menjadi stringFilter dan related posts rusakValidasi tipe frontmatter
Slug tidak konsistenAnchor link pecah per bahasaPakai slugger yang sama
Raw HTML dipercayaRisiko XSS lewat tag atau atributSanitize dengan schema
External link tidak dicekDokumen resmi pindah tanpa terlihatTes sebelum publish
Prompt terlalu luasFile worker lain ikut berubahKunci owned_files

Failure ini harus masuk prompt. Claude Code lebih stabil dengan batas yang bisa diuji daripada instruksi umum seperti “buat lebih berkualitas”.

Prompt aman untuk Claude Code

task: "Refresh one published MDX article"
owned_files:
  - "site/src/content/blog-id/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"

Cek sebelum publish dan CTA

Sebelum publish, jalankan script dan review manusia. Script memeriksa struktur, metadata, fences, link, dan kedalaman body. Reviewer memeriksa bahasa lokal, mobile readability, search intent, dan CTA.

node tools/audit-mdx.mjs site/src/content/blog-id/claude-code-markdown-processing.mdx
node tools/check-locales.mjs
node scripts/check-code-fences.mjs
node scripts/check-updated-article-quality.mjs

Untuk workflow individu, mulai dari cheatsheet Claude Code gratis. Jika perlu prompt review dan writing yang bisa dipakai ulang, gunakan Claude Code prompt templates. Untuk tim yang ingin menata permission, CI, locale workflow, dan editorial review, lanjut ke training dan konsultasi Claude Code.

Hasil verifikasi praktis

Dalam refresh ini, Masa memperlakukan artikel sebagai content pipeline, bukan sekadar rewrite prose. Checklist mencakup panjang description, updatedDate, heroImage, bahasa pada code fence, link resmi, kedalaman locale, dan CTA. Audit berbasis AST menangkap kasus yang sering lolos dari regex, terutama heading di dalam code block dan sintaks MDX dekat komponen. Perintah akhir adalah node scripts/check-code-fences.mjs dan node scripts/check-updated-article-quality.mjs. Pelajaran utamanya: Claude Code jauh lebih dapat diandalkan saat kontrak artikel bisa dieksekusi, bukan hanya saat prompt meminta “kualitas lebih baik”.

#Claude Code #Markdown #MDX #remark #operasi konten
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.