Skip to content

Repository files navigation

English · 한국어

The source of rinnadia.uk — my personal website.

React TypeScript Vite SCSS GitHub Actions License

Why this repo exists

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.

Stack

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

Architecture

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)

Engineering decisions

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.

Getting started

npm install
npm start          # Vite dev server → http://localhost:5173
npm run build      # Production build → /dist
npm run preview    # Serve the production build locally

prestart and prebuild regenerate public/posts/index.json automatically. To run it alone:

node scripts/build-posts-index.js

Adding a post

Drop 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.

Deployment

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.

Known trade-offs

Listed because a README that only lists strengths isn't a useful engineering document.

  • No test runner configured. src/App.test.tsx exists from the template, but no Jest/Vitest setup — for a site of this size, tsc --noEmit plus a visual check has been the honest cost/benefit call. Adding Vitest is the next step if the component logic grows.
  • Expertise and Publication are 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. mode is passed to Navigation via a parentToChild prop. 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.tsx is commented out — the form validates but doesn't submit. Email and LinkedIn are the working paths.

License

MIT. Code is free to reuse; the written content and personal information are not.


이 문서의 한국어 버전은 여기에서 읽으실 수 있습니다.

About

This is a Code Mirror version of my personal website, built with React 19, TypeScript, and Vite. The layout is customized from an MIT-licensed front-end template.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages