{
  "$type": "site.standard.document",
  "content": {
    "$type": "me.tompscanlan.content.markdown",
    "markdown": "This is the first post on a site that doesn't store its content in a repo.\n\nEvery page you read here — the resume, this blog post, the site config itself —\nis a record in [my AT Protocol PDS](https://enoki.us-east.host.bsky.network),\nfetched at build time and rendered to static HTML.\n\n## Why\n\nI've been building [OpenMeet](https://openmeet.net) on AT Protocol for two years.\nWhen I sat down to put up a personal site, the markdown-files-in-a-repo pattern felt\nwrong — I should be dogfooding the stack I'm building on.\n\nSo content lives in two collections:\n\n- `site.standard.publication` — site metadata (one record)\n- `site.standard.document` — every page, including this one\n\nBoth are [standard.site](https://standard.site) lexicons, used in production by\nLeaflet.pub and others. The bet is that \"long-form content as an atproto record\"\nbecomes a primitive that survives any one site or reader app.\n\n## How\n\nThe site is Astro. The interesting part is one file — `src/lib/atproto.ts` —\nwhich calls `com.atproto.repo.listRecords` against my PDS at build time and\nreturns typed records. The pages are dumb: they ask for a document by path, get\nback a record, and render its `content.markdown` field.\n\nPublishing is a tiny CLI:\n\n```\nnpm run publish:doc content/blog/new-post.md\n```\n\nThe script reads frontmatter for metadata, takes the markdown body, and calls\n`putRecord` with an `me.tompscanlan.content.markdown` content union. Idempotent\non path — re-publishing updates the same record.\n\n## Next\n\nThe natural next step is killing the build step. A Cloudflare Worker subscribed\nto the [atproto jetstream](https://github.com/bluesky-social/jetstream),\nfiltered to my DID, that fires the Pages deploy hook on every relevant record\nchange. Publish from anywhere, site updates within seconds, no git commit."
  },
  "description": "A personal site where every page is an atproto record. Built with Astro, standard.site lexicons, and Cloudflare Pages.",
  "path": "/blog/launching",
  "publishedAt": "2026-06-06T00:00:00.000Z",
  "site": "at://did:plc:cfo32kiwdectu33qzoyvrxex/site.standard.publication/self",
  "tags": [
    "atproto",
    "build-log",
    "standard.site"
  ],
  "textContent": "This is the first post on a site that doesn't store its content in a repo. Every page you read here — the resume, this blog post, the site config itself — is a record in my AT Protocol PDS, fetched at build time and rendered to static HTML. Why I've been building OpenMeet on AT Protocol for two years. When I sat down to put up a personal site, the markdown-files-in-a-repo pattern felt wrong — I should be dogfooding the stack I'm building on. So content lives in two collections: - — site metadata (one record) - — every page, including this one Both are standard.site lexicons, used in production by Leaflet.pub and others. The bet is that \"long-form content as an atproto record\" becomes a primitive that survives any one site or reader app. How The site is Astro. The interesting part is one file — — which calls against my PDS at build time and returns typed records. The pages are dumb: they ask for a document by path, get back a record, and render its field. Publishing is a tiny CLI: The script reads frontmatter for metadata, takes the markdown body, and calls with an content union. Idempotent on path — re-publishing updates the same record. Next The natural next step is killing the build step. A Cloudflare Worker subscribed to the atproto jetstream, filtered to my DID, that fires the Pages deploy hook on every relevant record change. Publish from anywhere, site updates within seconds, no git commit.",
  "title": "Launching tompscanlan.me on AT Protocol"
}