Langsung ke konten
KamusNgoding
Terapan React 5 menit baca

Membangun Blog Sederhana dengan React dan Markdown: Tutorial Step-by-Step

#react #project #markdown #tutorial #applied

Membangun Blog Sederhana dengan React dan Markdown: Tutorial Step-by-Step

Step-by-Step

Pendahuluan

Mempunyai portfolio atau blog developer adalah hal yang hampir wajib di era era sekarang. Bayangkan kamu ingin membangun platform konten seperti Medium Medium versi kecil milik sendiri — dengan React dan Markdown, kamu bisa mel melakukannya tanpa database sama sekali.

Tutorial ini akan membimbingmu membangun blog sederhana yang membaca file  .md` lokal, menampilkan daftar postingan, dan merender kontennya secara di dinamis. Stack yang digunakan: Vite + React, gray-matter (parsing f frontmatter), react-markdown (render konten), dan React Router DOM  (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 halaman
  • react-markdown — merender teks Markdown menjadi HTML
  • gray-matter — mem-parsing frontmatter YAML dari file .md
  • remark-gfm — plugin agar tabel, checkbox, dan sintaks GitHub Markdown d didukung

Jika kamu sudah familiar dengan [Tipe Data Dasar di TypeScript](/docs/sw/tyK TypeScript, kamu bisa mena menambahkan TypeScript ke proyek ini, namun untuk tutorial ini kita pakai J JavaScript biasa.


Konfigurasi untuk Membaca File Markdown

Vite mendukung import file mentah (raw string) dengan suffix ?raw. Artiny Artinya kita bisa mengimpor file .md langsung sebagai string tanpa plugin 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
**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
blog modern."
slug: "belajar-react"
---

## React dan Ekosistemnya

React punya ekosistem yang besar. Dengan komponen yang reusable, kita bisa 
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
}}>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 developer, seperti wiki teknis ala Confluence tapi lebih ringan. Dengan pen pendekatan ini, setiap engineer bisa menulis file .md, commit ke Git, dan dan konten langsung muncul di blog tanpa perlu database atau CMS.

Konsep yang sama juga bisa dikembangkan lebih jauh — misalnya jika ingin me membangun layanan seperti platform blog Kumparan atau Medium Indonesia, kam kamu tinggal mengganti sumber data dari file lokal ke API eksternal.

Kamu juga bisa mengintegrasikan blog ini dengan [Cara Menjalankan Tes Otoma Otomatis dengan GitHub Actions](/docs/devops/github-actions/cara-menjalanka52D Actions](/docs/devops/github-actions/cara-menjalankan-tes-otomatis-dengan-gActions agar setiap kali ada file .md baru di-push, blog otomatis t ter-deploy ke Vercel atau Netlify.

Selain itu, jika kamu sudah mahir [Menguasai Array Methods: Map, Filter, da dan Reduce di JavaScript](/docs/sw/javascript/menguasai-array-methods-map-f61D JavaScript](/docs/sw/javascript/menguasai-array-methods-map-filter-dan-reduJavaScript, kamu bisa menambahkan fitur filter postingan berdasarkan tag atau kateg 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) path) yang tidak tersedia di browser. Vite mencoba mem-bundle semua kode  termasuk gray-matter untuk browser.

Solusi: Pastikan gray-matter hanya digunakan di dalam utility yang di diimpor lewat import.meta.glob dengan mode eager. Jika masih error, tam 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 dengan benar. Bisa juga terjadi karena versi react-markdown v9+ mengubah  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 parameter dinamis, atau komponen PostPage tidak dibungkus dalam BrowserR BrowserRouter`.

Solusi: Periksa definisi route dan pastikan struktur berikut sudah bena 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/ 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 banyak file dengan pola glob secara native. require.context adalah fitur  Webpack. Keduanya melakukan hal serupa — batch import file — namun import. import.meta.glob` lebih modern, mendukung TypeScript, dan tidak memerlukan memerlukan plugin tambahan di Vite.

Bagaimana cara menambahkan fitur pencarian postingan?

Karena semua data sudah dimuat di memori browser melalui getAllPosts(), 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 (misalnya /nama-repo/). Kamu perlu mengatur base di vite.config.js da dan memastikan React Router menggunakan HashRouter alih-alih BrowserRout 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 teks mudah di-version-control dengan Git, tidak butuh hosting database, dan dan bisa diedit dengan teks editor apapun. Untuk blog personal atau dokumen dokumentasi tim kecil, ini jauh lebih praktis dibanding menyiapkan MySQL at atau PostgreSQL.

Bagaimana cara menambahkan syntax highlighting pada blok kode di Markdo

Markdown?

Gunakan plugin rehype-highlight atau rehype-prism-plus bersama react-m 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,  menampilkan daftar postingan, dan merender konten secara dinamis dengan rou routing. Dengan pendekatan ini, kamu punya blog yang sepenuhnya statik, mud mudah di-deploy ke mana saja, dan nyaman dikelola dengan Git.

Dari sini, kamu bisa mengembangkan lebih lanjut: tambahkan pagination, sist sistem tag, dark mode, atau bahkan integrasi dengan CMS headless seperti Co Contentful. Selamat bereksperimen dan terus bangun hal-hal keren! Jika ada  pertanyaan atau ingin eksplorasi topik React lainnya, KamusNgoding siap men menemanimu belajar.

Artikel Terkait