Membangun Blog Sederhana dengan React dan Markdown: Tutorial Step-by-Step[12D[K
Step-by-Step
Pendahuluan
Mempunyai portfolio atau blog developer adalah hal yang hampir wajib di era[3D[K era sekarang. Bayangkan kamu ingin membangun platform konten seperti Medium[6D[K Medium versi kecil milik sendiri — dengan React dan Markdown, kamu bisa mel[3D[K melakukannya tanpa database sama sekali.
Tutorial ini akan membimbingmu membangun blog sederhana yang membaca file [1D[K .md` lokal, menampilkan daftar postingan, dan merender kontennya secara di[2D[K
dinamis. Stack yang digunakan: Vite + React, gray-matter (parsing f[1D[K
frontmatter), react-markdown (render konten), dan React Router DOM [K
(navigasi).
Persiapan Proyek React dan Kebutuhan Dasar
Buat proyek baru dengan Vite:
npm create vite@latest blog-react -- --template react
cd blog-react
npm install
Instal dependensi yang dibutuhkan:
npm install react-router-dom react-markdown gray-matter remark-gfm
Penjelasan singkat tiap paket:
react-router-dom— untuk navigasi antar halamanreact-markdown— merender teks Markdown menjadi HTMLgray-matter— mem-parsing frontmatter YAML dari file.mdremark-gfm— plugin agar tabel, checkbox, dan sintaks GitHub Markdown d[1D[K didukung
Jika kamu sudah familiar dengan [Tipe Data Dasar di TypeScript](/docs/sw/ty[23DK TypeScript, kamu bisa mena[4D[K menambahkan TypeScript ke proyek ini, namun untuk tutorial ini kita pakai J[1D[K JavaScript biasa.
Konfigurasi untuk Membaca File Markdown
Vite mendukung import file mentah (raw string) dengan suffix ?raw. Artiny[6D[K
Artinya kita bisa mengimpor file .md langsung sebagai string tanpa plugin[6D[K
plugin tambahan.
Buat folder untuk menyimpan postingan:
mkdir src/posts
Buat dua file postingan contoh:
<!-- src/posts/hello-world.md -->
---
title: "Hello World dari Blogku"
date: "2026-03-01"
description: "Postingan pertama untuk mencoba blog React dan Markdown."
slug: "hello-world"
---
## Selamat Datang
Ini adalah postingan pertama. Blog ini dibangun dengan **React** dan **Mark[6D[K
**Markdown**.
Sangat mudah untuk ditulis!
<!-- src/posts/belajar-react.md -->
---
title: "Kenapa React Cocok untuk Blog?"
date: "2026-03-10"
description: "Alasan mengapa React adalah pilihan tepat untuk membangun blo[3D[K
blog modern."
slug: "belajar-react"
---
## React dan Ekosistemnya
React punya ekosistem yang besar. Dengan komponen yang reusable, kita bisa [K
membangun antarmuka blog dengan cepat dan efisien.
Sekarang buat utility untuk memuat semua postingan:
// src/utils/posts.js
import matter from 'gray-matter';
// Import semua file .md di folder posts secara eager (langsung dimuat)
const postModules = import.meta.glob('../posts/*.md', {
query: '?raw',
import: 'default',
eager: true,
});
export function getAllPosts() {
return Object.entries(postModules)
.map(([path, rawContent]) => {
const { data, content } = matter(rawContent);
const slug = path
.replace('../posts/', '')
.replace('.md', '');
return {
title: data.title || 'Tanpa Judul',
date: data.date || '',
description: data.description || '',
slug: data.slug || slug,
content,
};
})
.sort((a, b) => new Date(b.date) - new Date(a.date));
}
export function getPostBySlug(slug) {
const posts = getAllPosts();
return posts.find((post) => post.slug === slug) || null;
}
Membuat Komponen Postingan dari Markdown
Buat komponen yang merender konten Markdown:
// src/components/PostContent.jsx
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
export default function PostContent({ content }) {
return (
<article className="post-content">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{content}
</ReactMarkdown>
</article>
);
}
Buat juga komponen kartu untuk daftar postingan:
// src/components/PostCard.jsx
import { Link } from 'react-router-dom';
export default function PostCard({ title, date, description, slug }) {
return (
<div className="post-card">
<h2>
<Link to={`/post/${slug}`}>{title}</Link>
</h2>
<time className="post-date">{date}</time>
<p>{description}</p>
<Link to={`/post/${slug}`} className="read-more">
Baca Selengkapnya →
</Link>
</div>
);
}
Membuat Halaman Daftar Postingan
// src/pages/HomePage.jsx
import { getAllPosts } from '../utils/posts';
import PostCard from '../components/PostCard';
export default function HomePage() {
const posts = getAllPosts();
return (
<div className="container">
<header className="blog-header">
<h1>Blog Saya</h1>
<p>Kumpulan tulisan tentang teknologi dan pemrograman.</p>
</header>
<main className="post-list">
{posts.length === 0 ? (
<p>Belum ada postingan. Tambahkan file .md di folder posts!</p>
) : (
posts.map((post) => (
<PostCard key={post.slug} {...post} />
))
)}
</main>
</div>
);
}
Menambahkan Routing Dinamis untuk Setiap Postingan
Buat halaman detail postingan:
// src/pages/PostPage.jsx
import { useParams, Link, Navigate } from 'react-router-dom';
import { getPostBySlug } from '../utils/posts';
import PostContent from '../components/PostContent';
export default function PostPage() {
const { slug } = useParams();
const post = getPostBySlug(slug);
// Jika postingan tidak ditemukan, redirect ke 404
if (!post) {
return <Navigate to="/404" replace />;
}
return (
<div className="container">
<Link to="/" className="back-link">← Kembali ke Beranda</Link>
<article>
<header className="post-header">
<h1>{post.title}</h1>
<time className="post-date">{post.date}</time>
<p className="post-description">{post.description}</p>
</header>
<PostContent content={post.content} />
</article>
</div>
);
}
Sekarang konfigurasi router di main.jsx:
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import PostPage from './pages/PostPage';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/post/:slug" element={<PostPage />} />
<Route path="/404" element={<h1 style={{ textAlign: 'center' }}>404[6D[K
}}>404 - Halaman Tidak Ditemukan</h1>} />
</Routes>
</BrowserRouter>
</React.StrictMode>
);
Styling Blog dengan CSS Sederhana
Tambahkan styling dasar di src/index.css:
/* src/index.css */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', sans-serif;
background-color: #f9fafb;
color: #1f2937;
line-height: 1.7;
}
.container {
max-width: 760px;
margin: 0 auto;
padding: 2rem 1rem;
}
.blog-header {
text-align: center;
margin-bottom: 3rem;
}
.blog-header h1 {
font-size: 2.5rem;
color: #111827;
}
.post-card {
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
transition: box-shadow 0.2s;
}
.post-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.post-card h2 a {
color: #1d4ed8;
text-decoration: none;
font-size: 1.25rem;
}
.post-date {
color: #6b7280;
font-size: 0.875rem;
display: block;
margin: 0.25rem 0 0.75rem;
}
.read-more {
color: #2563eb;
font-weight: 600;
text-decoration: none;
}
.post-content {
margin-top: 2rem;
}
.post-content h2 {
margin: 2rem 0 1rem;
font-size: 1.5rem;
}
.post-content p {
margin-bottom: 1rem;
}
.post-content code {
background: #f3f4f6;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: monospace;
}
.post-content pre {
background: #1e293b;
color: #e2e8f0;
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
margin-bottom: 1.5rem;
}
.back-link {
display: inline-block;
margin-bottom: 1.5rem;
color: #4b5563;
text-decoration: none;
}
Contoh Kasus Nyata
Misalnya kamu ingin membangun platform dokumentasi internal untuk tim devel[5D[K
developer, seperti wiki teknis ala Confluence tapi lebih ringan. Dengan pen[3D[K
pendekatan ini, setiap engineer bisa menulis file .md, commit ke Git, dan[3D[K
dan konten langsung muncul di blog tanpa perlu database atau CMS.
Konsep yang sama juga bisa dikembangkan lebih jauh — misalnya jika ingin me[2D[K membangun layanan seperti platform blog Kumparan atau Medium Indonesia, kam[3D[K kamu tinggal mengganti sumber data dari file lokal ke API eksternal.
Kamu juga bisa mengintegrasikan blog ini dengan [Cara Menjalankan Tes Otoma[5D[K
Otomatis dengan GitHub Actions](/docs/devops/github-actions/cara-menjalanka52D[K
Actions](/docs/devops/github-actions/cara-menjalankan-tes-otomatis-dengan-gActions agar setiap kali ada file .md baru di-push, blog otomatis t[1D[K
ter-deploy ke Vercel atau Netlify.
Selain itu, jika kamu sudah mahir [Menguasai Array Methods: Map, Filter, da[2D[K dan Reduce di JavaScript](/docs/sw/javascript/menguasai-array-methods-map-f61D[K JavaScript](/docs/sw/javascript/menguasai-array-methods-map-filter-dan-reduJavaScript, kamu bisa menambahkan fitur filter postingan berdasarkan tag atau kateg[5D[K kategori langsung di sisi klien tanpa backend.
Troubleshooting: Error yang Sering Muncul
Module not found: gray-matter saat build Vite
Penyebab: gray-matter menggunakan modul Node.js native (fs, path)[7D[K
path) yang tidak tersedia di browser. Vite mencoba mem-bundle semua kode [K
termasuk gray-matter untuk browser.
Solusi: Pastikan gray-matter hanya digunakan di dalam utility yang di[2D[K
diimpor lewat import.meta.glob dengan mode eager. Jika masih error, tam[3D[K
tambahkan konfigurasi di vite.config.js:
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
optimizeDeps: {
include: ['gray-matter'],
},
define: {
'process.env': {},
},
});
Konten Markdown tampil sebagai teks biasa, bukan HTML
Penyebab: Komponen react-markdown belum dipasang atau tidak diimpor d[1D[K
dengan benar. Bisa juga terjadi karena versi react-markdown v9+ mengubah [K
cara import.
Solusi: Pastikan versi dan cara impor sudah sesuai:
# Cek versi yang terinstall
npm list react-markdown
// Cara import yang benar untuk react-markdown v8+
import ReactMarkdown from 'react-markdown';
// Gunakan sebagai komponen
<ReactMarkdown>{markdownString}</ReactMarkdown>
useParams mengembalikan undefined untuk slug
Penyebab: Path route di App.jsx tidak menggunakan :slug sebagai par[3D[K
parameter dinamis, atau komponen PostPage tidak dibungkus dalam BrowserR[9D[K BrowserRouter`.
Solusi: Periksa definisi route dan pastikan struktur berikut sudah bena[4D[K benar:
// Pastikan path menggunakan titik dua (:)
<Route path="/post/:slug" element={<PostPage />} />
// Di dalam PostPage, slug diambil dari useParams
const { slug } = useParams(); // bukan props
File .md tidak terbaca oleh import.meta.glob
Penyebab: Path glob pattern salah atau file berada di luar folder src/[5D[K src/`.
Solusi: Verifikasi path relatif dari file utility ke folder posts:
// Jika posts.js ada di src/utils/ dan folder posts ada di src/posts/
// Path harus relatif dari file utils:
const postModules = import.meta.glob('../posts/*.md', {
query: '?raw',
import: 'default',
eager: true,
});
// Cek hasilnya di console
console.log(Object.keys(postModules)); // harus menampilkan daftar file
Pertanyaan yang Sering Diajukan (FAQ)
Apa perbedaan import.meta.glob dengan require.context?
import.meta.glob adalah fitur khusus Vite yang memungkinkan import banyak[6D[K
banyak file dengan pola glob secara native. require.context adalah fitur [K
Webpack. Keduanya melakukan hal serupa — batch import file — namun import.[8D[K import.meta.glob` lebih modern, mendukung TypeScript, dan tidak memerlukan[10D[K
memerlukan plugin tambahan di Vite.
Bagaimana cara menambahkan fitur pencarian postingan?
Karena semua data sudah dimuat di memori browser melalui getAllPosts(), k[1D[K
kamu bisa menambahkan state filter di HomePage:
const [query, setQuery] = useState('');
const filtered = posts.filter(p =>
p.title.toLowerCase().includes(query.toLowerCase())
);
Tidak perlu backend — cukup filter array langsung di komponen.
Apakah blog ini bisa di-deploy ke GitHub Pages?
Bisa, namun ada catatan penting: GitHub Pages melayani dari subdirektori (m[2D[K
(misalnya /nama-repo/). Kamu perlu mengatur base di vite.config.js da[2D[K
dan memastikan React Router menggunakan HashRouter alih-alih BrowserRout[12D[K BrowserRouter`, karena GitHub Pages tidak mendukung HTML5 History API.
Mengapa menggunakan Markdown dibanding database untuk blog?
Markdown menawarkan workflow yang lebih sederhana untuk developer: file tek[3D[K teks mudah di-version-control dengan Git, tidak butuh hosting database, dan[3D[K dan bisa diedit dengan teks editor apapun. Untuk blog personal atau dokumen[7D[K dokumentasi tim kecil, ini jauh lebih praktis dibanding menyiapkan MySQL at[2D[K atau PostgreSQL.
Bagaimana cara menambahkan syntax highlighting pada blok kode di Markdo[6D[K
Markdown?
Gunakan plugin rehype-highlight atau rehype-prism-plus bersama react-m[8D[K react-markdown`:
npm install rehype-highlight highlight.js
import ReactMarkdown from 'react-markdown';
import rehypeHighlight from 'rehype-highlight';
import 'highlight.js/styles/github-dark.css';
<ReactMarkdown rehypePlugins={[rehypeHighlight]}>
{content}
</ReactMarkdown>
Kesimpulan
Kamu sudah berhasil membangun blog React yang membaca file Markdown lokal, [K menampilkan daftar postingan, dan merender konten secara dinamis dengan rou[3D[K routing. Dengan pendekatan ini, kamu punya blog yang sepenuhnya statik, mud[3D[K mudah di-deploy ke mana saja, dan nyaman dikelola dengan Git.
Dari sini, kamu bisa mengembangkan lebih lanjut: tambahkan pagination, sist[4D[K sistem tag, dark mode, atau bahkan integrasi dengan CMS headless seperti Co[2D[K Contentful. Selamat bereksperimen dan terus bangun hal-hal keren! Jika ada [K pertanyaan atau ingin eksplorasi topik React lainnya, KamusNgoding siap men[3D[K menemanimu belajar.