Back

Building PanhaInsight: My own Blog with Next.js

Building PanhaInsight: My own Blog with Next.js

Building PanhaInsight: A Khmer-First Blog with Next.js

Most blogs treat non-Latin scripts as an afterthought. I pick a nice English typeface, build the whole design system around it, and then — oh right — let's find a Khmer font that doesn't clash too badly. I wanted the opposite. PanhaInsight is built Khmer-first. The type scale, the spacing, the color system — they all start from how Khmer text reads and feels.

Here's how I built it.

The Problem

Khmer script is dense. Each character carries more visual weight than Latin, and the interconnected letter forms need more breathing room. Slap a Khmer font into a layout designed for English and you get cramped lines, awkward line breaks, and text that feels like it doesn't belong.

I read a lot of technical blogs. Most of them look the same — Medium clones with sterile layouts and thin grey text on white. The Khmer-language blogs that exist tend to use generic WordPress themes that weren't designed for the script either. I wanted something different: a reading experience that felt considered, where both Khmer and English felt like they belonged.

The Stack

I chose Next.js with the App Router for a few reasons:

  • Static generation — Blog content doesn't change per request. generateStaticParams() builds every post page at build time, so readers get instant loads.
  • Server components by default — Post rendering (reading markdown files, parsing frontmatter, converting to HTML) all happens on the server. No JavaScript shipped to the client for content.
  • Built-in metadata API — Open Graph tags, sitemaps, and RSS feeds are first-class citizens.

The markdown pipeline uses unified with remark-parse, remark-rehype, rehype-highlight, and rehype-sanitize. This means I write posts in markdown, code blocks get syntax highlighting, and the output is sanitized HTML. Simple and secure.

The Design System

This is where PanhaInsight differs from most blog setups. Instead of reaching for Tailwind or a component library, I wrote a custom CSS design system using custom properties.

Typography

The type scale uses clamp() with a 1.3 ratio, designed so Khmer text remains readable at every breakpoint. Four Google Fonts are loaded:

| Font | Role | |------|------| | Noto Serif Khmer | Headings — gives Khmer text editorial weight | | Kantumruy Pro | Body text — legible and warm for long reads | | Noto Sans Khmer | UI elements — clean and functional | | JetBrains Mono | Code blocks — monospaced for clarity |

The base size is set with Khmer readability in mind. Khmer characters need more horizontal space than Latin, so line-height and letter-spacing are tuned accordingly.

Color

The color system uses OKLCH with hex fallbacks for older browsers. The palette is dark-first — not because dark mode is trendy, but because it's easier on the eyes for long reading sessions. The accent color is an amber-gold (#e5a832) that feels warm without being loud.

Restraint

The brand personality is restrained, warm, intentional. No gradients, no glassmorphism cards, no CTA banners. The texture comes from the content itself — the rhythm of headings, the spacing between paragraphs, the way code blocks sit in the flow. Every visual element earns its place. If removing it improves the page, it goes.

Content as Files

Posts are markdown files in content/posts/. Each has YAML frontmatter for metadata:

---
title: "រៀនរស់"
date: "2026-02-14"
summary: "រៀនរស់ នឹងមានការចែករំលែកដល់អ្នកដទៃ"
author: "Panha"
tags: ["Education", "Advice"]
---

The slug comes from the filename. No database, no CMS, no admin panel — just files. This keeps things simple and portable. I write in my editor, commit, and push. Vercel builds and deploys.

Accessibility

Accessibility isn't optional when your audience reads Khmer. Khmer script requires larger minimum font sizes for readability — what's legible in English at 14px is not legible in Khmer at the same size. The design system accounts for this.

I also added:

  • prefers-reduced-motion support — all transitions and animations are disabled for users who prefer it
  • Focus-visible outlines using the accent color
  • ARIA roles on interactive elements like the tag filter
  • Sufficient contrast ratios tested against OKLCH perceptual lightness

SEO and Distribution

Static generation means every page is pre-rendered HTML. Beyond that:

  • Sitemap auto-generated at /sitemap.xml
  • RSS feed at /rss.xml
  • Open Graph metadata on every post page, so links look good when shared
  • A global metadata template: %s | PanhaInsight

No plugins, no overhead. Just Next.js API routes generating XML at build time.

What I Learned

Building this blog taught me a few things:

  1. Start from the content, not the framework. I designed the type system around how Khmer text reads before I wrote a single React component. The result feels more intentional than retrofitting a template.

  2. Custom CSS beats utility classes for editorial design. When your design system has five font roles, OKLCH color tokens, and Khmer-specific spacing rules, a 747-line globals.css file with custom properties gives you more control than any utility framework.

  3. Static is fast enough. No database queries, no server rendering at request time. Just pre-built HTML files served from a CDN. The entire page loads in under a second.

  4. Simplicity compounds. Markdown files, a unified pipeline, no admin panel, no auth, no database. Each simplification removes a surface area for bugs and a layer of maintenance. The blog just works.

What's Next

PanhaInsight is a living project. The podcast page is waiting to be filled. More posts are coming — in both Khmer and English. The design system will evolve as I learn more about how Khmer readers interact with the layout.

If you're building something for a bilingual audience, I hope this encourages you to design for both languages from the start — not as an afterthought.


Find more posts at PanhaInsight. Thanks for reading.