All About My Personal Site
Building a personal site is a rite of passage for developers. It's a playground where you can experiment with new technologies, showcase your work, and share your thoughts. This post documents how I built this site from the ground up, the technology choices I made, and why.
#Link to this headingThe Tech Stack
#Link to this headingFramework: Next.js 15+ with App Router
I chose Next.js as the foundation for several reasons:
- First-class support for React Server Components
- Excellent developer experience with fast refresh
- Built-in routing, image optimization, and API routes
- Great ecosystem and community support
The App Router introduced a mental model that feels more natural than the old getServerSideProps pattern. Any component can handle backend work directly, making the architecture cleaner and more intuitive.
#Link to this headingContent: MDX for Interactive Posts
MDX gives me the full power of React while writing content. I can embed interactive components, code playgrounds, and custom visualizations directly in my markdown:
// I can use React components directly in MDX
<Aside variant="info">
This is a custom callout component!
</Aside>The content lives in the repository under content/ with a structured hierarchy:
content/
├── Course/
│ └── 6th-Semester/
│ └── IF3270/
│ ├── index.mdx
│ └── 1.mdx
├── Tech/
│ └── Language/
│ └── JavaScript/
│ └── closures.mdx
├── Personal/
│ └── 1.mdx ← You are here!
└── Competition/For Personal writings, I opted for a flat structure - no nested folders. Just simple files that map directly to URLs like /writings/personal/all-about-my-personal-site.
#Link to this headingStyling: Linaria (Zero-Runtime CSS-in-JS)
I use Linaria for styling, which compiles to CSS at build time with zero runtime overhead. This was important for React Server Component compatibility - traditional CSS-in-JS libraries like styled-components require client-side JavaScript.
import { styled } from '@linaria/react';
const Card = styled.div`
padding: 1.5rem;
border-radius: 8px;
background: var(--color-card-bg);
box-shadow: 0 2px 8px var(--color-shadow-light);
`;The tradeoff? Linaria's ecosystem is smaller, and Next.js sometimes bundles more CSS than necessary. But the benefits of zero-runtime styling outweigh these concerns for my use case.
#Link to this headingSyntax Highlighting: Shiki via rehype-pretty-code
For code blocks, I use Shiki through rehype-pretty-code. Unlike Prism which runs at runtime and adds ~26kb to your bundle, Shiki compiles syntax highlighting at build time:
- Zero JavaScript overhead for syntax highlighting
- Support for dozens of languages without bundle bloat
- VS Code-quality highlighting with proper tokenization
- Dual theme support (light/dark) built-in
#Link to this headingMath Rendering: KaTeX
For mathematical notation, I integrated KaTeX through remark-math and rehype-katex. It supports both inline math like and display equations:
KaTeX renders at build time, so there's no client-side math processing overhead.
#Link to this headingAnimation: React Spring
For fluid, physics-based animations, I use React Spring. It provides a declarative API for animations that feel natural:
const spring = useSpring({
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'translateY(0)' : 'translateY(24px)',
});#Link to this headingContent Architecture
#Link to this headingMDX Processing Pipeline
The content pipeline processes MDX files through several stages:
- Parsing:
gray-matterextracts frontmatter metadata - Remark plugins: Transform markdown (GFM tables, math syntax)
- Rehype plugins: Transform HTML (syntax highlighting, KaTeX)
- Compilation:
next-mdx-remote/rsccompiles to React components
const { content } = await compileMDX({
source: rawContent,
options: {
mdxOptions: {
remarkPlugins: [remarkGfm, remarkMath],
rehypePlugins: [
[rehypePrettyCode, { theme: { light: 'github-light', dark: 'dracula' } }],
rehypeKatex,
],
},
},
components: {
// Custom component mappings
Aside,
CodeSnippet,
// Markdown element replacements
a: TextLink,
pre: CodeBlock,
code: InlineCode,
},
});#Link to this headingAutomatic Metadata Extraction
Several metadata fields are computed automatically:
- Reading time: Calculated using
reading-timepackage (~200 words/min) - Preview: Auto-extracted from the first paragraph if not provided
- Table of Contents: Headings extracted via regex for navigation
- URL slugs: Generated from titles using consistent slugification
#Link to this headingCategory System
Content is organized into four categories:
| Category | Structure | Use Case |
|---|---|---|
| Course | Nested (Folder/Parent) | Academic notes, organized by semester |
| Tech | Nested (Folder/Parent) | Technical articles, grouped by topic |
| Competition | Nested (Folder/Parent) | CTF writeups, hackathon projects |
| Personal | Flat | Thoughts and reflections |
#Link to this headingTheme System
#Link to this headingDual-Layer Implementation
The theme system works on two layers to prevent flash of unstyled content:
- Server-side: An inline script reads the theme cookie and applies it before hydration
- Client-side:
ThemeProvidermanages state and provides theuseTheme()hook
// In layout.tsx - runs before React hydrates
<script dangerouslySetInnerHTML={{
__html: `
const theme = document.cookie.match(/theme=(\w+)/)?.[1] || 'light';
document.documentElement.classList.toggle('dark', theme === 'dark');
`
}} />#Link to this headingCSS Custom Properties
All colors are defined as CSS custom properties, making theme switching seamless:
:root {
--color-background: #ffffff;
--color-text: #000000;
}
.dark {
--color-background: #121212;
--color-text: #ffffff;
}#Link to this headingLessons Learned
#Link to this headingWhat Worked Well
- MDX flexibility: Being able to embed React components in content is powerful
- Static generation: Build-time rendering keeps the site fast
- CSS variables: Make theming trivial and performant
- TypeScript everywhere: Catches errors early and improves DX
#Link to this headingChallenges Faced
- App Router learning curve: The new mental model takes adjustment
- Linaria quirks: Some Next.js integration issues required workarounds
- MDX v3 migration: Breaking changes required careful plugin updates
#Link to this headingFuture Improvements
- Full-text search across all content
- View count and engagement metrics
- RSS feed generation
- More interactive components and demos
#Link to this headingConclusion
Building this site has been a rewarding journey through modern web development. The combination of Next.js, MDX, and zero-runtime CSS-in-JS creates a powerful foundation for content-rich sites.
The best part? Everything is in the repository. No CMS, no external dependencies for content. Just markdown files that compile to a fast, accessible website.
If you're building your own blog or personal site, I hope this breakdown helps inform your technical decisions. Feel free to explore the source code(opens in new tab) and reach out with questions!
Membangun situs pribadi adalah ritual bagi para developer. Ini adalah tempat bermain di mana kamu bisa bereksperimen dengan teknologi baru, memamerkan karyamu, dan berbagi pemikiranmu. Postingan ini mendokumentasikan bagaimana saya membangun situs ini dari nol, pilihan teknologi yang saya buat, dan alasannya.
#Link to this headingTech Stack
#Link to this headingFramework: Next.js 15+ dengan App Router
Saya memilih Next.js sebagai fondasi karena beberapa alasan:
- Dukungan kelas satu untuk React Server Components
- Pengalaman developer yang sangat baik dengan fast refresh
- Routing bawaan, optimasi gambar, dan API routes
- Ekosistem dan dukungan komunitas yang hebat
App Router memperkenalkan mental model yang terasa lebih natural dibanding pola getServerSideProps yang lama. Setiap komponen bisa menangani pekerjaan backend secara langsung, membuat arsitektur lebih bersih dan intuitif.
#Link to this headingKonten: MDX untuk Postingan Interaktif
MDX memberikan saya kekuatan penuh React saat menulis konten. Saya bisa menyematkan komponen interaktif, code playground, dan visualisasi kustom langsung di markdown:
// Saya bisa menggunakan komponen React langsung di MDX
<Aside variant="info">
Ini adalah komponen callout kustom!
</Aside>Konten disimpan di repository dalam folder content/ dengan hierarki terstruktur:
content/
├── Course/
│ └── 6th-Semester/
│ └── IF3270/
│ ├── index.mdx
│ └── 1.mdx
├── Tech/
│ └── Language/
│ └── JavaScript/
│ └── closures.mdx
├── Personal/
│ └── 1.mdx ← Kamu di sini!
└── Competition/Untuk tulisan Personal, saya memilih struktur flat - tanpa folder bersarang. Hanya file sederhana yang langsung dipetakan ke URL seperti /writings/personal/all-about-my-personal-site.
#Link to this headingStyling: Linaria (Zero-Runtime CSS-in-JS)
Saya menggunakan Linaria untuk styling, yang dikompilasi ke CSS saat build time dengan zero runtime overhead. Ini penting untuk kompatibilitas React Server Component - library CSS-in-JS tradisional seperti styled-components membutuhkan JavaScript di sisi klien.
import { styled } from '@linaria/react';
const Card = styled.div`
padding: 1.5rem;
border-radius: 8px;
background: var(--color-card-bg);
box-shadow: 0 2px 8px var(--color-shadow-light);
`;Tradeoff-nya? Ekosistem Linaria lebih kecil, dan Next.js terkadang membundle lebih banyak CSS dari yang diperlukan. Tapi manfaat zero-runtime styling lebih besar dari kekhawatiran ini untuk kasus penggunaan saya.
#Link to this headingSyntax Highlighting: Shiki via rehype-pretty-code
Untuk code blocks, saya menggunakan Shiki melalui rehype-pretty-code. Berbeda dengan Prism yang berjalan saat runtime dan menambah ~26kb ke bundle, Shiki mengkompilasi syntax highlighting saat build time:
- Zero JavaScript overhead untuk syntax highlighting
- Dukungan untuk puluhan bahasa tanpa bundle bloat
- Highlighting berkualitas VS Code dengan tokenisasi yang tepat
- Dukungan dual theme (light/dark) bawaan
#Link to this headingMath Rendering: KaTeX
Untuk notasi matematika, saya mengintegrasikan KaTeX melalui remark-math dan rehype-katex. Ini mendukung math inline seperti dan persamaan display:
KaTeX merender saat build time, jadi tidak ada overhead pemrosesan math di sisi klien.
#Link to this headingAnimasi: React Spring
Untuk animasi berbasis fisika yang halus, saya menggunakan React Spring. Ini menyediakan API deklaratif untuk animasi yang terasa natural:
const spring = useSpring({
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'translateY(0)' : 'translateY(24px)',
});#Link to this headingArsitektur Konten
#Link to this headingPipeline Pemrosesan MDX
Pipeline konten memproses file MDX melalui beberapa tahap:
- Parsing:
gray-mattermengekstrak metadata frontmatter - Remark plugins: Mentransformasi markdown (tabel GFM, sintaks math)
- Rehype plugins: Mentransformasi HTML (syntax highlighting, KaTeX)
- Kompilasi:
next-mdx-remote/rscmengkompilasi ke komponen React
#Link to this headingEkstraksi Metadata Otomatis
Beberapa field metadata dihitung secara otomatis:
- Waktu baca: Dihitung menggunakan package
reading-time(~200 kata/menit) - Preview: Auto-extracted dari paragraf pertama jika tidak disediakan
- Daftar Isi: Heading diekstrak via regex untuk navigasi
- URL slugs: Dihasilkan dari judul menggunakan slugifikasi konsisten
#Link to this headingSistem Kategori
Konten diorganisir dalam empat kategori:
| Kategori | Struktur | Kegunaan |
|---|---|---|
| Course | Nested (Folder/Parent) | Catatan akademik, diorganisir per semester |
| Tech | Nested (Folder/Parent) | Artikel teknis, dikelompokkan per topik |
| Competition | Nested (Folder/Parent) | CTF writeups, proyek hackathon |
| Personal | Flat | Pemikiran dan refleksi |
#Link to this headingSistem Tema
#Link to this headingImplementasi Dual-Layer
Sistem tema bekerja di dua layer untuk mencegah flash of unstyled content:
- Server-side: Script inline membaca cookie tema dan menerapkannya sebelum hydration
- Client-side:
ThemeProvidermengelola state dan menyediakan hookuseTheme()
#Link to this headingCSS Custom Properties
Semua warna didefinisikan sebagai CSS custom properties, membuat pergantian tema mulus:
:root {
--color-background: #ffffff;
--color-text: #000000;
}
.dark {
--color-background: #121212;
--color-text: #ffffff;
}#Link to this headingPelajaran yang Dipetik
#Link to this headingApa yang Berjalan Baik
- Fleksibilitas MDX: Bisa menyematkan komponen React di konten sangat powerful
- Static generation: Rendering saat build time menjaga situs tetap cepat
- CSS variables: Membuat theming trivial dan performant
- TypeScript di mana-mana: Menangkap error lebih awal dan meningkatkan DX
#Link to this headingTantangan yang Dihadapi
- Learning curve App Router: Mental model baru membutuhkan penyesuaian
- Keunikan Linaria: Beberapa masalah integrasi Next.js membutuhkan workaround
- Migrasi MDX v3: Breaking changes membutuhkan update plugin yang hati-hati
#Link to this headingPeningkatan di Masa Depan
- Pencarian full-text di semua konten
- View count dan metrik engagement
- Generasi RSS feed
- Lebih banyak komponen interaktif dan demo
#Link to this headingKesimpulan
Membangun situs ini telah menjadi perjalanan yang bermanfaat melalui pengembangan web modern. Kombinasi Next.js, MDX, dan zero-runtime CSS-in-JS menciptakan fondasi yang kuat untuk situs kaya konten.
Bagian terbaiknya? Semuanya ada di repository. Tidak ada CMS, tidak ada dependensi eksternal untuk konten. Hanya file markdown yang dikompilasi menjadi website yang cepat dan accessible.
Jika kamu sedang membangun blog atau situs pribadimu sendiri, saya harap breakdown ini membantu menginformasikan keputusan teknismu. Silakan jelajahi source code(opens in new tab) dan hubungi jika ada pertanyaan!