A minimal, fast static site generator for my personal blog. It was inspired by Hugo, built with TypeScript, Vite toolchain, and Tailwind CSS.
- Markdown posts with YAML frontmatter
- Syntax highlighting via highlight.js (GitHub Dark Dimmed theme)
- Tailwind CSS v4 with
@tailwindcss/typographyfor pretty prose - Auto-extracted metadata — title, date, tags, author, description, reading time
- Categories — automatically generated from posts' metadata
- Tags — automatically generated from posts' metadata
- GitHub Pages ready — includes a CI/CD workflow
my-blog/
├── content/
│ └── posts/ <- blog markdowns
├── scripts/
│ ├── types.ts <- shared TS types
│ ├── parser.ts <- markdown + frontmatter parser
│ ├── renderer.ts <- HTML template functions
│ └── build.ts <- build orchestrator (dev + prod)
├── src/
│ └── styles/
│ └── main.css <- tailwind entry + all custom CSS (mostly not written by me)
├── public/ <- static assets copied as-is to dist/
├── dist/ <- generated output (not meant to be edited manually)
├── .github/
│ └── workflows/
│ └── deploy.yml <- GitHub Actions CI/CD
├── package.json <- dependencies
└── tsconfig.json <- TS configuration
npm installnpm run devThis starts three concurrent processes:
- build watcher — rebuilds HTML when markdown or templates change
- CSS watcher — rebuilds Tailwind CSS on source change
- local server — serves
dist/at http://localhost:3000
npm run buildGenerates optimized output in dist/. Preview it locally:
npm run previewCreate a new .md file in content/posts/:
---
title: "My New Post"
date: "2026-01-31"
description: "A short summary shown in listings."
category: "category-name"
tags: ["tag-one", "tag-two"]
author: "Your Name"
draft: false
---
Your post content here...The filename becomes the URL slug. my-new-post.md -> /posts/my-new-post/.
| Field | Required | Description |
|---|---|---|
title |
yes | Post title |
date |
yes | ISO date string (YYYY-MM-DD) |
description |
no | Excerpt for listings and meta tags |
tags |
no | Array of strings |
author |
no | Defaults to "Anonymous" |
draft |
no | true hides post in prod builds |
Fenced code blocks with a language identifier get syntax-highlighted:
```typescript
const greeting = (name: string) => `Hello, ${name}!`;
```Edit getSiteConfig() in scripts/build.ts:
function getSiteConfig(): SiteConfig {
return {
title: 'My Blog',
description: 'A personal blog about code and ideas.',
author: 'Your Name',
baseUrl: process.env['BASE_URL'] ?? '',
year: new Date().getFullYear(),
};
}All styling lives in src/styles/main.css. The design tokens (colors, fonts, spacing) are defined as CSS custom properties in the @theme block at the top of the file. Change them to theme the whole site.
To add a new static page (e.g. /uses/):
- Add a
renderUses(config: SiteConfig): stringfunction inscripts/renderer.ts - Call it in
scripts/build.tsinside thebuild()function:
writeFile(path.join(OUT_DIR, 'uses', 'index.html'), renderUses(config));- Add a nav link in the
navbar()function inrenderer.ts
| Command | Description |
|---|---|
npm run dev |
Dev server + watchers at localhost:3000 |
npm run build |
Production build to dist/ |
npm run preview |
Serve dist/ locally |
npm run type-check |
TypeScript type checking |