The personal technical website of Daniel Beck, Senior Software Engineer. It is a durable, self-owned platform for long-form writing, YouTube, and open source, where each article anchors a content cluster: post, video, repo, LinkedIn, talk.
Stack: Astro (static, no adapter) · MDX content collections · Tailwind CSS v4 · self-hosted fonts · build-time OG images · deployed on Cloudflare Pages. No CMS, no database.
Start with docs/README.md and docs/ai-agent-guide.md. The short version is:
- Identity lives in
src/consts/site.ts. - Content lives in collections and data files, not page templates.
- Visible copy follows docs/writing/voice-and-tone.md.
- Run
npm run checkbefore you finish. agent-map.jsongives a machine-readable map of the same rules and file paths.
| Command | Action |
|---|---|
npm install |
Install dependencies |
npm run dev |
Start the dev server at localhost:4321 |
npm run build |
Build the production site to ./dist/ |
npm run preview |
Preview the production build locally |
npm run check |
Type-check + validate content collection schemas |
npm run format |
Format with Prettier |
Requires Node >= 22.12 (see .nvmrc, which pins 24 for Cloudflare).
src/
├── consts/ site.ts (identity + nav) · categories.ts · contributions.ts (tracked OSS repos)
├── content.config.ts Zod schemas for the collections (blog · videos · speaking)
├── content/ blog/*.mdx · speaking/*.md
├── data/ videos.yaml (videos collection)
├── lib/ content.ts (queries + cross-link checks) · contributions.ts (GitHub PRs) · seo.ts · date.ts
├── layouts/ BaseLayout · BlogPostLayout
├── components/ mdx/ (MDX components) · layout/ · blog/ · home/ · youtube/ · speaking/ · opensource/ · seo/ · ui/
├── pages/ routes (incl. blog tag/category pagination, rss.xml, og/[...route].ts)
└── styles/ global.css ← design tokens, dark-mode flip, prose, Shiki theming
All identity lives in src/consts/site.ts: email, GitHub, LinkedIn, X,
YouTube, handles, and channel id. Change it once and it updates the header,
footer, contact page, Person JSON-LD, OG tags, and RSS. GitHub, LinkedIn, X, and
YouTube are real accounts; only email (hello@danielbeck.dev) is still a
placeholder to swap before launch.
-
Create
src/content/blog/<slug>.mdx. -
Add frontmatter. A bad
categoryor dangling cross-link fails the build:--- title: 'Your Title' description: 'One-sentence summary (also used for OG + cards).' pubDate: 2026-06-20 category: Design Systems # one of the 9 in src/consts/categories.ts tags: [react, tokens] draft: true # hidden in production until set false # Optional reputation-system cross-links: video: { youtubeId: 'abc123', title: 'Watch the walkthrough' } repo: { url: 'https://github.com/you/repo', name: 'you/repo', language: 'TypeScript' } talk: { title: 'My Talk', event: 'SomeConf' } discussion: { linkedinUrl: 'https://www.linkedin.com/posts/...' } ---
-
Write in MDX. These components are available with no imports:
<Callout type="warning" title="...">,<MermaidDiagram code={\...`} />,,,,,,`. Plain fenced code is syntax-highlighted automatically (dual light/dark themes, with a copy button). -
npm run devto preview. Flipdraft: falsewhen ready. -
Commit and push. Cloudflare rebuilds. Tags, categories, RSS, sitemap, and the OG image regenerate automatically.
Videos live in src/data/videos.yaml and talks/podcasts in
src/content/speaking/*.md; their relatedPost fields wire up cross-links,
validated at build time.
The Open Source page lists your merged pull requests to external libraries,
pulled live from GitHub at build time. You only maintain a list of repos in
src/consts/contributions.ts: add an owner/name entry and the page
auto-populates that repo's PRs, description, stars, and language:
export const trackedRepos = [{ repo: 'QwikDev/partytown' }, { repo: 'module-federation/vite' }];The author is taken from siteConfig.githubUsername. Without a token the fetch
works at low volume, but GitHub may rate-limit CI IPs. In that case each repo
still renders with its fallback blurb and a link to the live PR list, so the
page is never empty. Set GITHUB_TOKEN in Cloudflare for reliable builds.
This is a fully static site: no adapter, no Worker script. It deploys to
Cloudflare Workers as static assets, configured by wrangler.jsonc:
The project/worker name cannot contain a dot (lowercase letters, numbers,
and dashes only), so it is danielbeckdev. The domain danielbeck.dev is added
separately as a custom domain.
- Push to GitHub.
- Cloudflare dashboard: Workers & Pages, then Import a repository, and
pick
DanBeckDev/danielbeck.dev. - Build command
npm run build; the build deploys via the committedwrangler.jsonc. SetNODE_VERSION = 24. - Every push to
mainrebuilds and deploys. - Add the custom domain
danielbeck.devunder the project's Domains & Routes.
Classic Cloudflare Pages also works (build npm run build, output dist);
either way the project name cannot contain a dot.
Set a GITHUB_TOKEN build environment variable in Cloudflare so the Open Source
page reliably fetches your merged PRs (see above). A read-only token with no
scopes is enough; it only reads public data. Without it the build still
succeeds; the contributions section just falls back to repo links.
{ "name": "danielbeckdev", "compatibility_date": "2026-06-13", "assets": { "directory": "./dist" }, }