Ton Story
This site. Portfolio + bilingual blog on Next 15, Tailwind v4, MDX.
The site you're looking at. Bilingual EN/TH editorial layout, three themes
(milk, coffee, tea), and a deliberately unstyled /bio page as a small
joke for anyone who reads source.
What it is
- One repo. Next.js 15 App Router, React 19.
- Posts and projects as
.mdxfiles incontent/. Posts are single-language (EN or TH set vialang:frontmatter); projects come as paired.en.mdx/.th.mdxfiles. - Three themes (milk / coffee / tea) wired through CSS variables. No JS
class swapping — just
data-themeon the html element, the rest is CSS. - IBM Plex Serif + Plex Sans Thai Looped, picked so EN and TH glyphs share an x-height and a baseline rhythm.
Why I built it from scratch
The previous version was a starter template It hard to adjust, never grew into. Editing it felt like decorating a hotel room. Building from scratch let the structure match how I actually write — and let me make boring decisions on purpose (no CMS, no analytics, no auth).
How the bilingual works
One language at a time per page. EN is the canonical: the bare URL
(/projects/class-peek) is English. Thai gets a /th/ prefix
(/th/projects/class-peek). A small EN | TH switcher in the header
preserves the current path when you flip — no broken deep links across
languages.
Blog posts are single-language by frontmatter. The index shows all of them
regardless of switcher state, with a small EN/TH chip per card so the
reader sees the full archive and self-selects. The brand mark
Aritoton · อริโตต้น is the one place that stays bilingual side-by-side
on purpose.
Stack-specific notes
- Tailwind v4 with
@themefor tokens. The same tokens drive the three themes and, separately, Class Peek's reverse lookup. - MDX via
next-mdx-remote/rsc— posts render server-side, fully static, zero client runtime for prose. - Sitemap emits bare and
/th/URLs per page with hreflang alternates. One canonical per language, no duplicate-content trouble.