Migrasi Blog ke MDX dengan Shiki dan Rehype

Migrasi Blog ke MDX dengan Shiki dan Rehype

Perjalanan migrasi website portfolio & blog pribadi saya ke MDX dengan Shiki dan Rehype

Arman Dwi Pangestu
Arman Dwi PangestuFebruary 12, 2026
0 views
4 min read

Pendahuluan

Selama dua tahun terakhir, saya mengandalkan Next.js, TailwindCSS, dan Markdown standar untuk menggerakkan portfolio pribadi dan blog saya. Meskipun cukup membantu, saya sering merasa frustrasi dengan keterbatasan blok kode saya. Menambahkan fitur lanjutan—seperti yang kita lihat di dokumentasi tingkat atas—terasa seperti perjuangan yang berat. Saya kehilangan:

  • Syntax highlighting yang akurat yang benar-benar memahami kode.
  • Nomor baris dan line/word highlighting untuk keterbacaan yang lebih baik.
  • Judul kustom atau bar seperti jendela untuk kesan premium.
  • Kelompok kode untuk menampilkan beberapa file atau opsi bersama-sama.
  • Notasi diff, baris fokus, dan kode inline yang menonjol.
  • Tombol "Copy" dan "Wrap" untuk meningkatkan pengalaman pengguna.

Akar masalahnya adalah highlight.js. Meskipun populer, cukup terbatas; mengandalkan regex untuk highlighting, yang sering melewatkan nuansa sintaks modern.

Pada akhir 2025, saya akhirnya memiliki ruang untuk mengoptimalkan situs saya. Setelah menggali bagaimana situs dokumentasi modern—seperti Nextra, Code Hike, Expressive Code, dan Vite Press—menangani konten, pemenang yang jelas muncul: MDX dikombinasikan dengan Shiki dan Rehype.

Perubahan bukan hanya tentang syntax highlighting. Saya membutuhkan kemampuan untuk menyematkan komponen React interaktif langsung dalam posting saya. Markdown standar tidak bisa mengikuti.

Eksekusi: Coba dan Gagal

Jalan menuju implementasi jauh dari mulus. Awalnya, saya berjuang untuk menyatukan ekosistem Shiki dan Rehype. Namun, semuanya berhasil ketika saya menemukan panduan fantastis oleh Jolly Coding.

Video tersebut memperkenalkan saya pada permata bernama Velite. Jika Anda belum mendengarnya, Velite adalah pipeline konten yang kuat yang tidak hanya "membaca" file—ia menelan, memvalidasi, mengubah, dan memperkaya konten Anda menjadi data yang ketat.

Memikirkan Kembali Alur Konten

Sebelum Velite, alur saya sederhana namun rapuh:

Alur Lama
File Markdown -> Render langsung

Dengan Velite, ia berubah menjadi pipeline yang kuat dan terstruktur:

VelitePipeline Velite
Markdown/MDX

Parse Frontmatter

Validasi Skema (Zod)

Transformasi Field

Tambah Computed Fields

Generate Tipe

Export Konten Terstruktur

Berikut adalah rincian visual tentang bagaimana Velite menangani pekerjaan berat:

Selain otomasi, Velite membuat konten type-safe dengan menggunakan skema Zod. Ini berarti tidak ada lagi kesalahan "undefined" atau frontmatter yang rusak. Berikut adalah gambaran konfigurasi yang saya gunakan untuk blog ini:

import { defineConfig} from "velite"
import rehypeSlug from "rehype-slug"
import rehypePrettyCode from "rehype-pretty-code"
import rehypeAutolinkHeadings from "rehype-autolink-headings"
import { transformerNotationDiff, transformerNotationHighlight, transformerNotationFocus, transformerNotationErrorLevel } from "@shikijs/transformers"
import { transformerTwoslash, rendererRich } from "@shikijs/twoslash"
import { posts } from "./velite/collection/posts"
import { pages } from "./velite/collection/pages"
import { projects } from "./velite/collection/projects"
import { rehypePreMeta } from "./velite/rehype/plugins/rehype-pre-meta"
import { rehypeCodeGroup } from "./velite/rehype/plugins/rehype-code-group"
 
export default defineConfig({
    root: "content",
    output: {
        data: ".velite",
        assets: "public/static",
        base: "/static/",
        name: "[name]-[has:6].[ext]",
        clean: true
    },
    collections: {
        posts,
        pages,
        projects
    },
    mdx: {
        rehypePlugins: [
            rehypeSlug, 
            [rehypePrettyCode, 
                { 
                    theme: {
                        light: "material-theme-lighter",
                        dark: "material-theme-ocean"
                    },
                    defaultLang: {
                        block: "plaintext"
                    },
                    transformers: [
                        transformerTwoslash({
                            explicitTrigger: true,
                            renderer: rendererRich(),
                        }),
                        transformerNotationDiff({ matchAlgorithm: 'v3' }),
                        transformerNotationHighlight({ matchAlgorithm: 'v3' }),
                        transformerNotationFocus({ matchAlgorithm: 'v3' }),
                        transformerNotationErrorLevel({ matchAlgorithm: 'v3' }),
                    ],
                }
            ],
            rehypeCodeGroup,
            rehypePreMeta,
            [rehypeAutolinkHeadings, 
                {
                    behavior: "wrap",
                    properties: {
                        className: ["subheading-anchor"],
                        ariaLabel: "Link to section"
                    }
                }
            ]
        ],
        remarkPlugins: [],
    }
})

Hasilnya: Blog yang Dirancang Ulang

Setelah beberapa penyesuaian, saya berhasil membangun blog statis dengan fitur yang menyaingi dokumentasi profesional. Berikut adalah beberapa sorotan:

Callout Interaktif

Isyarat visual sangat penting untuk pengalaman membaca yang baik. Saya mengimplementasikan komponen Callout—terinspirasi oleh blockquote GitHub—untuk menyoroti catatan, tips, dan peringatan.

Bagaimana tampilannya di MDX:

mdx
<Callout type="note" descriptionColor="normal">
    <p>Ini adalah contoh callout catatan.</p>
</Callout>

Bagaimana tampilannya saat dirender:

Note

Ini adalah contoh callout catatan.

Pengalaman Kode Premium

Dengan memanfaatkan Shiki dan Rehype Pretty Code, blok kode tidak lagi teks statis—mereka adalah ruang kerja interaktif.

Tip

Dalam contoh di bawah, saya menggunakan bash sebagai bahasa untuk menampilkan kode MDX mentah tanpa dieksekusi oleh renderer. Tetapi dalam implementasi nyata menggunakan tsx sebagai bahasa.

Sintaks MDX:

mdx
```bash icon="iTsx" title="Counter.tsx" caption="Contoh counter reaktif" showLineNumbers /useState/#v /useEffect/#s /React/#i /{count}/ wrap=false wrapToggleButton
// [!code focus:1]
import React, { useState, useEffect } from 'react';
 
function Counter() {
  // [!code focus:2]
   // [!code --:1]
   const [count, setCount] = useState(0);
   // [!code ++:1]
   const [count, setCount] = useState<number>(0);
 
  // [!code focus:3]
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
 
  return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}
```

Hasilnya:

Counter.tsx
import React, { useState, useEffect } from 'react';
 
function Counter() {
   const [count, setCount] = useState(0);
   const [count, setCount] = useState<number>(0);
 
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
 
  return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}
Contoh counter reaktif

Intelijen TypeScript dengan Twoslash

Salah satu penambahan paling keren adalah integrasi Twoslash. Ini membawa kekuatan compiler TypeScript langsung ke posting blog Anda.

Tip

Coba: Arahkan kursor ke atas kode yang digarisbawahi di bawah untuk melihat definisi tipe!

Contoh Twoslash
interface Todo {
  Todo.title: stringtitle: string
}
 
const const todo: Readonly<Todo>todo: type Readonly<T> = { readonly [P in keyof T]: T[P]; }
Make all properties in T readonly
Readonly
<Todo> = {
title: stringtitle: 'Delete inactive users'.String.toUpperCase(): string
Converts all the alphabetic characters in a string to uppercase.
toUpperCase
(),
} const todo: Readonly<Todo>todo.title = 'Hello'
Cannot assign to 'title' because it is a read-only property.
var Number: NumberConstructor
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number
.p
  • parseFloat
  • parseInt
  • prototype
NumberConstructor.parseInt(string: string, radix?: number): number
Converts A string to an integer.
@paramstring A string to convert into a number.@paramradix A value between 2 and 36 that specifies the base of the number in `string`. If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal. All other strings are considered decimal.
arseInt
('123', 10)

Kesimpulannya

Membuka Sumber

Meskipun situs portfolio saya bersifat closed source, saya ingin memberikan kembali kepada komunitas. Saya telah membuat template open-source bernama Velora yang mencakup semua fitur ini.

Repository dilengkapi dengan dokumentasi lengkap dan pipeline CI/CD, menjadikannya titik awal yang bagus untuk proyek Anda sendiri.

Mengapa Shiki & Rehype Memenangkan Hati Saya

Setelah menjelajahi alternatif seperti highlight.js, Prism.js, dan Code Hike, Shiki menonjol karena satu alasan utama: Akurasi.

Shiki menggunakan TextMate Grammars, mesin yang sama yang menggerakkan VS Code. Ini memastikan bahwa apa yang Anda lihat di editor Anda adalah persis apa yang pembaca Anda lihat di blog. Plus, Shiki bersifat statis. Tidak seperti highlight.js atau Prism.js, yang berjalan di browser dan dapat memperlambat, Shiki melakukan semua pekerjaan selama proses build.

Rehype Pretty Code mengambil highlighting mentah itu dan menambahkan "polish"—nomor baris, highlighting, dan atribut kustom. Dikombinasikan dengan transformers dan Twoslash, ini adalah setup kelas dunia untuk blog teknis apa pun.

Jika Anda ingin meningkatkan permainan konten Anda, migrasi ke MDX adalah investasi yang terbayar setiap kali pembaca mengklik "copy", mengarahkan kursor ke definisi tipe, dan berinteraksi dengan blok kode.