English · 한국어
The source of rinnadia.uk — my personal website.
Most of my production work lives behind an NDA — billing systems, internal platforms, pipelines nobody outside the company will ever see. A résumé compresses that into bullet points and loses the part that actually matters: the reasoning.
So this site is deliberately built rather than bought. It's a small project, but it's held to the same standard as production work: understand the constraint, pick the least clever thing that satisfies it, keep the build reproducible. The sections below document the decisions, including the ones I'd defend in a design review and the trade-offs I knowingly accepted.
| Layer | Choice | Why |
|---|---|---|
| UI | React 19 + TypeScript 5.6 (strict) |
Strict mode from the start — type errors are cheaper than runtime ones |
| Build | Vite 8 (@vitejs/plugin-react) |
Fast HMR, minimal config, no ejected toolchain to maintain |
| Routing | react-router v8 (BrowserRouter) |
Two content surfaces (home, articles) — a router is warranted; a framework is not |
| Styling | SCSS, per-component files in src/assets/styles/ |
No CSS-in-JS runtime cost for a static site |
| Components | MUI 9 (AppBar, Drawer, TextField, Chip) | Used selectively for accessible primitives, not as a design system |
| Content | react-markdown + remark-gfm / remark-math / rehype-highlight / rehype-katex |
Posts include math and code, so the pipeline supports both |
| Hosting | GitHub Pages + custom domain (CNAME) |
Static output, zero running cost, no server to secure |
src/
├── App.tsx # Router + dark/light mode state
├── components/
│ ├── Main.tsx # Hero, contact links
│ ├── Expertise.tsx # Core competencies
│ ├── Timeline.tsx # Professional experience (vertical timeline)
│ ├── Articles.tsx # Latest posts on the home view
│ ├── ArticlesList.tsx # Search, sort, tag filter, pagination
│ ├── Post.tsx # Single post renderer
│ ├── Navigation.tsx # AppBar + mobile Drawer
│ ├── FadeIn.tsx # Staggered entrance animation
│ └── index.ts # Barrel export
├── utils/posts.ts # Memoised index fetch + lazy body loader
└── assets/styles/ # One SCSS file per component
public/posts/ # Markdown source of truth
scripts/build-posts-index.js # Build-time metadata generator
Routes: / (home) · /articles-list (index) · /articles-list/:slug (post)
These are the parts worth a reviewer's attention.
1. Build-time metadata index instead of a CMS or a runtime scan.
scripts/build-posts-index.js walks public/posts/*.md, parses front matter, computes reading time and excerpt fallbacks, skips drafts, and emits index.json. It runs automatically via the npm prestart / prebuild lifecycle hooks — so npm run build in CI can't drift from local. It's dependency-free (Node built-ins only): a Markdown blog didn't justify adding a parser to the dependency tree and its supply-chain surface.
2. The list is fast because bodies are lazy.
index.json carries metadata only. Full Markdown bodies are fetched per-post by loadPostBody when a post is opened, and getPostIndex() memoises the index in a module-level promise so navigation doesn't refetch.
3. I measured the limit rather than guessing it.
Benchmarked against synthetic post sets: generation costs ~0.07s at 100 posts and ~0.5s at 1,000 — invisible next to the Vite build. The real constraint is the ~460 bytes of metadata per post in index.json, which is fetched up front. Verdict: comfortable to ~500 posts, worth revisiting past ~1,000. Documented rather than prematurely optimised.
4. SPA routing on a static host.
GitHub Pages has no rewrite rules, so deep links like /articles-list/some-post would 404. The predeploy step copies dist/index.html to dist/404.html, which Pages serves for unknown paths — the SPA then routes client-side. Cheapest correct fix; no server required.
5. Korean typography is a layout problem, not a font problem.
.post-body carries a Hangul-capable fallback chain (Apple SD Gothic Neo, Malgun Gothic, Noto Sans KR) using system fonts only — no webfont request, no CLS. Paired with word-break: keep-all, because Korean has no English-style word boundaries and the browser default wraps mid-word, which reads as broken text to a native speaker.
npm install
npm start # Vite dev server → http://localhost:5173
npm run build # Production build → /dist
npm run preview # Serve the production build locallyprestart and prebuild regenerate public/posts/index.json automatically. To run it alone:
node scripts/build-posts-index.jsDrop a Markdown file into public/posts/. The filename becomes the slug.
---
title: Your Title
date: 2026-08-13
tags: [Engineering, AI]
excerpt: One or two sentences for the article list.
draft: false
---Every field is optional — the generator falls back to the slug for title, file mtime for date, and an auto-extracted excerpt. Set draft: true to keep a post out of the index.
Push to main → GitHub Actions (.github/workflows/deploy.yml) runs npm ci, npm run build, and publishes dist/ via peaceiris/actions-gh-pages. npm run deploy does the same manually through gh-pages.
Listed because a README that only lists strengths isn't a useful engineering document.
- No test runner configured.
src/App.test.tsxexists from the template, but no Jest/Vitest setup — for a site of this size,tsc --noEmitplus a visual check has been the honest cost/benefit call. Adding Vitest is the next step if the component logic grows. ExpertiseandPublicationare near-identical. Kept as deliberate duplicates: two components that look alike today but change for different reasons don't earn a shared abstraction yet.- Prop drilling for theme state.
modeis passed toNavigationvia aparentToChildprop. Fine at this depth; it would need Context before a second cross-cutting concern (language) joins it. - Contact form doesn't send. EmailJS integration in
Contact.tsxis commented out — the form validates but doesn't submit. Email and LinkedIn are the working paths.
MIT. Code is free to reuse; the written content and personal information are not.
이 문서의 한국어 버전은 여기에서 읽으실 수 있습니다.