- Replace the create-next-app README with real project docs (features, env vars, frontmatter schema, Markdown extensions, deployment) - Drop ESLint (unused by any script; oxlint is the linter) and the unrelated 'hast' package; move oxlint, oxfmt and puppeteer to devDependencies (locked versions unchanged) - Extract PostList, used by the tag and category pages - Use shared Intl formatters for post dates, pinned to Europe/Prague so the build server's timezone can't shift dates - Use <h1> for the tag/category page headings (same styling) - Remove math/mermaid flags from posts that don't use them; fix a typo in the Tessera cover alt text
3.8 KiB
Ephemeris
Source of blog.hangerthem.com — my personal blog and portfolio. Posts are plain Markdown files rendered to static HTML at build time by a custom unified pipeline.
Features
- Fully static — every post, tag and category page is pre-rendered with Next.js (App Router, React Server Components)
- Custom Markdown extensions via small rehype plugins in
lib/:- Callouts:
> [!TIP],> [!WARNING],> [!INFO],> [!QUOTE] - Highlights:
==marked text== - Definition lists (a term line followed by
: definitionlines) - Code blocks with language label, line numbers and a copy button
- Callouts:
- Math with KaTeX and diagrams with Mermaid, rendered to SVG at build time — no client-side JS for either
- Table of contents generated from
h2/h3headings with scroll-spy - SEO — canonical URLs, Open Graph/Twitter cards, generated OG images, JSON-LD, sitemap, robots.txt and an Atom feed
- Works without JavaScript; JS-only UI (like copy buttons) is hidden when it's unavailable
Stack
Next.js 16 · React 19 · TypeScript · Tailwind CSS 4 · unified (remark/rehype) · highlight.js · KaTeX · Mermaid (via Puppeteer) · Bun · oxlint/oxfmt
Getting started
bun install
SITE_URL=http://localhost:3000 bun dev
| Script | Purpose |
|---|---|
bun dev |
Development server |
bun run build |
Production build (static generation) |
bun start |
Serve the production build |
bun run lint |
oxlint + formatting check |
bun run lint:fix |
Auto-fix lint issues and format |
Environment variables
| Variable | Required | Description |
|---|---|---|
SITE_URL |
yes | Public origin, e.g. https://blog.hangerthem.com |
SOURCE_COMMIT |
no | Commit hash shown in the sidebar (falls back to git rev-parse) |
GIT_ORIGIN_URL |
no | Repository URL; enables the "Source Code" link and commit link |
Writing posts
Posts live in content/posts/; the file name is the URL slug. Standalone pages (About, Projects, Setup) live in content/pages/.
---
title: 'Tessera: A Privacy-First Card Wallet'
description: 120–160 characters work best for search results.
date: 2026-09-18 12:00:00 +0200
updated: 2026-09-18 12:30:00 +0200 # optional
category: Projects
tags: [projects, pwa, privacy]
coverImage: tessera-cover.png # optional, from public/posts/, 1200×630 recommended
coverImageAlt: Tessera logo next to two loyalty cards
math: true # optional, enables KaTeX
mermaid: true # optional, enables Mermaid diagrams
draft: true # optional, only rendered in development
---
Frontmatter is validated at build time, so a missing or malformed field fails the build with the file name instead of producing a broken page.
Images go in public/posts/ and are referenced by file name: .
Project structure
app/ Routes, metadata, feed, sitemap and OG image generation
components/ UI split into content/, layout/, post/ and ui/
content/ Markdown posts and pages
lib/ Markdown pipeline, rehype plugins, tag/category helpers
public/posts/ Post images
Deployment
Deployed with Nixpacks (see nixpacks.toml). Mermaid diagrams are rendered with headless Chromium during the build, so the build environment needs Chromium's system libraries and network access to fetch the Mermaid script.