Shipped the blog in one evening

A year into running @techaroundsports. Some things live longer than a single post — this is the shelf.

Shipped daniel.csylabs.com tonight. Zero to production.

What's inside: → 21 notes on launch — converted the Telegram archive to MDX → RU/EN, toggle top-right, translation pairing via translationOf in frontmatter → Dark mode, moon icon in the menu, localStorage remembers the choice → Clickable tags, one page per tag → RSS, sitemap, hreflang — no CMS, no database, MDX in git → Reading-first layout: IBM Plex Sans 18px body, Unbounded Black display, 48rem max column

Stack: → Next.js 16.2 + React 19.2 + Tailwind v4 + MDX → In the csylabs-platform monorepo, package apps/daniel-blog, port 3020 → Caddy in front, Docker Compose beside, GitHub Actions deploys on push to main → Analytics — self-hosted Umami, cookieless, referrers only

What broke (and why it's interesting):

Docker Hub doesn't pull from Moscow. node:20-slim died three times on TLS handshake. My first diagnosis — anonymous rate limit — was wrong; the real cause was geo-blocking of Servers.ru IP ranges. The host-side fix is one file:

/etc/docker/daemon.json:
{"registry-mirrors": ["https://mirror.gcr.io"]}

Google runs an anonymous read-through cache of Docker Hub's library/* namespace, reliably reachable from Russian infrastructure. systemctl restart docker and every docker pull tries Google first, falling back to Docker Hub only on mirror miss.

MDX hates <url> autolinks. Parser sees < and expects a JSX component. Error: "Unexpected character / before local name". Workaround: rewrite github.com/x not as <https://github.com/x> but as [github.com/x](https://github.com/x). One-shot pass across 21 MDX files, three replacements.

Tailwind Typography won a specificity race. I bound my palette via .prose-blog { --tw-prose-body: var(--fg) }, but body text kept rendering muted gray. The plugin injects its own .prose { --tw-prose-body: #364153 } into @layer utilities, while my block lived in @layer components — cascade order wasn't in my favour. Fix: tighten the selector to .prose.prose-blog (specificity 0,2,0 beats the plugin's 0,1,0).

Dark mode ignored the toggle. Tailwind v4's dark: modifier defaults to @media (prefers-color-scheme: dark), not the [data-theme="dark"] attribute my toggle sets. Users on light OS clicking the moon got nothing. Fix was one line: @custom-variant dark (&:where([data-theme="dark"] *)) in globals.css rewires the variant to the attribute.

Next: → This week: tune layout, hunt browser bugs, add OG images for social previews → May: finish the iPhone-SRT write-up (sub-3-second latency over cellular) → May 22 — Nepal, first paid mobile-production event. Hero post lands from there

Shelf is open. More as things land.

Related reading