achmadya.dev
Terbuka untuk pekerjaan
COMMAND PALETTE

Cari sesuatu

10 hasilGunakan tautan di bawah untuk membuka halaman
Proyek
proyekMandor PlateA reusable SaaS boilerplate with an API, dashboard, database, and tests in one monorepo.proyekMCP QueryA suite of MCP servers for querying Excel and four databases over npx and stdio, with a small runtime and explicit error handling.
Tulisan
TulisanRecording Finances in a Spreadsheet with HermesHow Hermes maps messages to cashflow rules, prepares reviewable drafts, requests confirmation, and writes and verifies spreadsheet transactions.TulisanOrganizing Coding Workflows with Hermes ProjectsHow to connect a Git repository to a Hermes Project, prepare its runtime and dependencies, and run validation from consistent working context.TulisanHow I render Markdown and Mermaid in ReactThe rendering pipeline I use for safe Markdown, highlighted code, and responsive Mermaid diagrams.TulisanDesigning an MCP tool call I can traceHow I separate protocol handling, database adapters, and public errors in a small MCP query server.TulisanInstalling Hermes Agent and Understanding Its ArchitectureA complete guide to installing Hermes Agent and understanding profiles, skills, tools, gateway, schedules, Kanban, memory, and agent architecture.TulisanA monorepo as a context boundary for AIWhat changed when I put contracts, backend, frontend, and tests in one workspace for AI-assisted development.TulisanLearning Microsoft SQL Server and its backup mechanismNotes on learning Microsoft SQL Server through an online-store case: from containers and queries to recovery models, backup chains, and restore operations.TulisanBuilding CCTV Live Streaming and Playback on the Web with FFmpegR&D notes on taking Hikvision video from RTSP to the browser, including H.265 transcoding, MPEG-TS, WebSocket delivery, and time-based playback.
~/tulisan / rendering-markdown-and-mermaid

Cara saya merender Markdown dan Mermaid di React

ReactMarkdownMermaid

Masalahnya bukan parsing Markdown

Merender Markdown dengan react-markdown cukup mudah sampai satu fenced code block harus berubah menjadi komponen interaktif.

Saya ingin code block biasa tetap memiliki syntax highlighting, sedangkan block mermaid berubah menjadi diagram SVG. Diagram yang salah juga harus memiliki fallback tanpa merusak artikel.

Implementasi awal memperlakukan semua code block dengan cara yang sama. Source Mermaid tampil sebagai teks, sedangkan perbaikan CSS berikutnya membuat ukuran diagram tidak konsisten.

Pertanyaan desain yang lebih berguna adalah: di mana proses Markdown berhenti dan renderer diagram mulai bekerja?

Pipeline rendering

Pipeline saat ini memberi satu tanggung jawab pada setiap tahap:

Merender diagram...

remark-gfm menambahkan table, task list, strikethrough, dan fitur GitHub-flavored Markdown. rehype-sanitize membuang HTML berbahaya sebelum output mencapai React.

rehype-highlight membentuk token untuk code block biasa. Mermaid dimasukkan ke daftar plain text agar source-nya diteruskan utuh, bukan dibungkus span untuk highlighting.

<ReactMarkdown
  components={{ pre: MarkdownPre }}
  rehypePlugins={[
    rehypeSanitize,
    [rehypeHighlight, { plainText: ["mermaid", "text", "txt"] }],
  ]}
  remarkPlugins={[remarkGfm]}
>
  {source}
</ReactMarkdown>

Raw HTML tetap dinonaktifkan. Konten portfolio tetap merupakan input, meskipun filenya berada di repository yang sama dengan aplikasi.

Mendeteksi Mermaid pada boundary pre

Fenced block dirender menjadi <pre><code class="language-mermaid">...</code></pre>. Jika hanya elemen code yang diganti, diagram tetap berada di dalam pre dan mewarisi spacing serta overflow milik code block.

Karena itu, saya memeriksa child pada boundary pre. Block Mermaid mengembalikan komponen, sedangkan bahasa lain tetap mengembalikan elemen pre semula.

function MarkdownPre({ children, ...props }: PreProps) {
  const child = Children.toArray(children)[0];

  if (isValidElement<CodeProps>(child)) {
    const classes = (child.props.className ?? "").split(/\s+/);

    if (classes.includes("language-mermaid")) {
      const chart = String(child.props.children ?? "").replace(/\n$/, "");
      return <MermaidDiagram chart={chart} />;
    }
  }

  return <pre {...props}>{children}</pre>;
}

Boundary kecil ini mencegah beberapa masalah styling. Code biasa tetap mengikuti GitHub Markdown CSS, sedangkan diagram mendapat container khusus SVG.

Memuat Mermaid hanya di browser

Mermaid jauh lebih besar daripada renderer Markdown dan bergantung pada browser API. Import pada module load akan menambah pekerjaan pada halaman tanpa diagram dan menyulitkan server rendering.

Komponen diagram mengimpor Mermaid di dalam effect. Promise pada level module memastikan library hanya dimuat dan diinisialisasi satu kali.

let mermaidPromise: Promise<MermaidApi> | undefined;

function loadMermaid() {
  mermaidPromise ??= import("mermaid").then(({ default: mermaid }) => {
    mermaid.initialize({
      securityLevel: "strict",
      startOnLoad: false,
      suppressErrorRendering: true,
      theme: "base",
    });
    return mermaid;
  });

  return mermaidPromise;
}

Setiap render memakai ID unik. SVG yang dihasilkan dimasukkan ke container khusus, lalu binding function dari Mermaid dijalankan setelah SVG tersedia.

Effect juga menyimpan status aktif komponen. Jika navigasi menghapus komponen sebelum render selesai, hasilnya dibuang dan tidak ditulis ke elemen yang sudah tidak digunakan.

Membuat kegagalan tetap terlihat

Renderer dokumentasi sebaiknya tidak menyembunyikan source yang salah. Jika Mermaid melempar error, komponen menampilkan detail yang dapat dibuka bersama source diagram.

Merender diagram...

Fallback ini membantu saat menulis. Kesalahan tetap berada pada satu diagram dan artikel lain masih dapat dirender.

Styling tanpa melawan SVG

Artikel memakai github-markdown-css untuk spacing dokumen dan theme GitHub dari Highlight.js untuk warna token. Override hanya digunakan agar font tetap mengikuti IBM Plex milik situs.

Mermaid membutuhkan aturan terpisah. Wrapper mengatur padding, border, dan horizontal overflow. Scope .markdown-body juga menimpa margin horizontal untuk figure dari github-markdown-css; tanpa override ini, diagram selebar artikel akan bergeser keluar pada layar kecil. SVG mengikuti lebar yang tersedia sambil mempertahankan rasio aspek.

.markdown-body .mermaid-wrapper {
  width: 100%;
  margin: 2rem 0;
  overflow-x: auto;
  overflow-y: hidden;
}

.markdown-body .mermaid-diagram {
  min-width: 0;
  width: 100%;
}

.markdown-body .mermaid-diagram svg {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}

Saya juga menetapkan theme variable Mermaid secara eksplisit. ERD membutuhkan rowOdd dan rowEven; variable atribut yang namanya mirip tidak dipakai oleh versi renderer di project ini.

Detail tersebut mengingatkan saya untuk memeriksa SVG dan source renderer sebelum menambah lebih banyak CSS override.

Bagian yang saya pertahankan

Renderer Markdown tidak perlu mengetahui cara Mermaid menggambar diagram. Ia hanya mengenali satu class bahasa, lalu menyerahkan source kepada komponen lain.

Komponen Mermaid tidak perlu memahami Markdown. Ia menerima chart, mengatur lifecycle browser, lalu mengembalikan SVG atau error.

Pemisahan ini menjaga kedua jalur tetap sederhana. Syntax highlighting tetap bekerja untuk TypeScript dan bahasa lain, sementara Mermaid dapat diubah tanpa mengganti pipeline Markdown.

Pelajaran utamanya bukan urutan plugin tertentu. Hal yang lebih penting adalah memilih satu titik handoff, mempertahankan jalur default, dan membuat kegagalan mudah diperiksa.