A personal blog about life, engineering, and games. Built with Astro, styled with a custom "Engineering Logbook" editorial design, and deployed to Netlify.
- Astro (static output) with content collections for articles and talks
- Hand-written CSS design tokens (no Tailwind) — light mode, three typefaces
(Space Grotesk / Newsreader / JetBrains Mono) self-hosted via
@fontsource - Shiki for code highlighting,
remark-reading-timefor read times - RSS via
@astrojs/rss; sitemap via@astrojs/sitemap; contact form via Netlify Forms - Per-article social share cards rendered at build time with satori + resvg
(
src/pages/og/[slug].png.js);public/og-default.pngcovers the non-article pages - Client-side ⌘K search palette (
SearchPalette.astro) over an inlined index — no backend /stats— the log in numbers (entries, words, read time, per-year and tag breakdowns)
npm install
npm run dev # local dev server
npm run build # production build → dist/
npm run preview # preview the built site
npm run check # type-check (astro check)
npm run test # unit tests (vitest)Articles live in src/content/articles/*.md. Frontmatter:
idx: 1 # stable publication number (ascending by date)
title: "…"
date: 2020-10-02T00:00:00Z
slug: "the-beauty-of-node-streams" # the URL path (overrides the filename)
tags: ["nodejs", "streams"]
excerpt: "…"
draft: false
featured: false
featuredImage: "../../assets/images/stream.jpg" # local asset, optional
# coverImage: "https://…" # remote image (dev.to imports), optional
# canonicalUrl / devtoUrl # set on imported articlesslug preserves the article's public URL — do not change it for existing posts.
Talks are listed in src/content/talks/talks.json.
- Get your dev.to API key (Settings → Extensions → API Keys).
DEVTO_API_KEY=xxx SITE_URL=https://your-site npm run import:devto- Re-run anytime — existing files are skipped when either the slug or the
article title already exists locally, so your hand-migrated posts are not
duplicated. Pass
-- --forceto overwrite. Note:--forcerewrites existing files and re-assignsidxfrom the current max, so it can renumber posts. Prefer the default (skip) for incremental imports; only use--forcefor a full re-sync and review the diff. - Review the new
src/content/articles/*.md(andgit status), then commit.
The blog is the canonical source; imported articles carry a devtoUrl that renders
an "also on dev.to ↗" mirror link.
Privacy-friendly, cookieless GoatCounter. Set the
PUBLIC_GOATCOUNTER environment variable (in the Netlify UI) to your count endpoint,
e.g. https://abcdev.goatcounter.com/count. When unset, no analytics script is
emitted, so local dev and previews stay tracking-free. The script also skips
localhost on its own. No cookie-consent banner is required.
Pushed to Netlify via CI. netlify.toml pins the build (npm run build → dist/)
and Node 22.