How This Site Works
This page explains the technical architecture of my personal site — how it's built, why I made certain choices, and how you can modify it.
Why I Built This
I wanted a personal site that was:
- Fast — No frameworks, minimal JavaScript, optimized assets
- Easy to maintain — Update content by dropping markdown files in a folder
- Type-safe — TypeScript throughout for fewer bugs
- Automatable — Deploy with a single git push
- Personal — Fully owned code I understand completely
Most site builders add bloat. I decided to write my own, and it's only 44KB total.
The Build Pipeline
When I run npm run build, here's what happens:
TypeScript Compilation
↓
SCSS → CSS Compilation
↓
CSS Minification (cssnano, -33%)
↓
JavaScript Minification (terser, -46%)
↓
Markdown Processing
↓
HTML Generation & Minification
↓
SEO Files (sitemap.xml, robots.txt)
↓
Optimized dist/ folder
Each step is intentional. The final output is 44KB of pure optimized HTML/CSS/JS.
Content System
I write content in Markdown. Each file can include optional metadata:
---
title: My Project Title
order: 10
---
# The actual content
Goes here...
The order field controls navigation order (lower numbers first). The title field becomes the page heading and navigation label.
All markdown files automatically become sections in my site navigation. No database, no complex setup — just files in a folder.
Theme System
Originally, this site picked a random color theme on every page load (dark blue, teal, orange, etc.). That felt chaotic.
I redesigned the theme system to be intentional:
- Dark mode — Black (#0a0a0a) background, cream text (#fffaf0)
- Light mode — Cream (#fffaf0) background, black text (#0a0a0a)
- System detection — Respects your OS dark mode preference
- Manual override — Toggle button in the header lets you choose
The theme is controlled by CSS custom properties in src/assets/scss/_variables.scss. Changing the theme is one line of code.
Mobile Experience
Screens under 600px get a different layout:
- Top header collapses to an icon bar
- Navigation moves to a Floating Action Button (FAB) — that round button in the bottom-right
- Tap it to see all navigation links
- All interaction is vanilla JavaScript, no libraries
Code Organization
src/
├── assets/
│ ├── ts/
│ │ └── main.ts # Runtime code (theme toggle, FAB)
│ ├── scss/
│ │ ├── _variables.scss # CSS variables & themes
│ │ ├── _theme-dark.scss # Dark mode overrides
│ │ └── _components.scss # Component styles
│ └── icons/ # SVG assets
├── scripts/
│ ├── generate-md.ts # Main build tool
│ ├── lib/ # Shared modules
│ │ ├── constants.ts
│ │ ├── utils.ts
│ │ ├── markdown.ts
│ │ └── seo.ts
│ └── tests/ # Test suite
└── index.html # Page template
Build Scripts
The main build script is generate-md.ts (94 lines, down from 194 before refactoring). It:
- Finds all
.mdfiles incontent/ - Parses YAML front-matter
- Converts markdown to HTML
- Injects into the template
- Minifies output
- Generates
robots.txtandsitemap.xml
Shared logic lives in src/scripts/lib/ — constants, utilities, markdown processing, and SEO generation. This keeps the main script clean and reusable code out of the way.
Client Code
src/assets/ts/main.ts handles runtime behavior:
- Detects system dark mode preference
- Stores your manual theme choice in localStorage
- Toggles theme when you click the header button
- Controls the mobile FAB menu visibility
Total: ~60 lines of vanilla JavaScript. No frameworks needed.
Testing Approach
I have two types of tests:
Integration Tests
Run the markdown generator and verify it produces correct HTML. Catches regressions if I break the build pipeline.
Visual Regression Tests
Uses Puppeteer to screenshot the site in light and dark modes. Compares against baseline images to catch unintended visual changes.
Screenshots are stored in src/tests/output/snapshots/ for comparison and debugging.
Run both with npm run test.
Deployment
GitHub Actions handles everything:
- Linting — ESLint checks TypeScript, Stylelint checks SCSS
- Testing — Builds, runs tests, verifies no regressions
- Minification — CSS (-33%), JS (-46%), HTML (-42%)
- Deploy — Uploads
dist/to GitHub Pages
Workflow file: .github/workflows/static.yml
Everything is automated. I just push code, GitHub Actions runs checks, and if tests pass, the site deploys automatically.
Performance Details
Final asset sizes after optimization:
- CSS: 17KB → 11KB (cssnano, -33%)
- JavaScript: 3.7KB → 2KB (terser, -46%)
- HTML: 12KB → 7KB (html-minifier, -42%)
Total: 44KB of final assets. Fast load times on all devices.
Data & Telemetry
Zero tracking. I don't run analytics, use cookies, or collect data. Just a fast, simple site.
How to Extend This
Want to modify it?
- Add a new page: Drop a
.mdfile incontent/and rebuild - Change colors: Edit
src/assets/scss/_variables.scss - Modify mobile layout: Edit
src/assets/scss/_fab.scss - Add functionality: Write TypeScript in
src/assets/ts/main.ts
All changes feed through the build pipeline automatically.
Technical Stack
- TypeScript — Type-safe scripts and client code
- SCSS — Organized stylesheets with variables and themes
- Remark/Rehype — Markdown-to-HTML processing
- Puppeteer — Visual regression testing
- PostCSS — CSS minification
- Terser — JavaScript minification
- ESLint 9 — Code linting with flat config
- GitHub Actions — CI/CD automation
No frameworks. No build tools beyond npm scripts. Everything is explicit and understandable.
Last Updated
December 20, 2025 — Commit 7ece2a5