{
  "$type": "site.standard.document",
  "description": "Keeping it consistent.",
  "path": "/2026-03-10-hello-astro/",
  "publishedAt": "2026-03-10T14:58:04.247Z",
  "site": "at://did:plc:ex23caczr45rodrfcxrwps6h/site.standard.publication/self",
  "tags": [
    "website"
  ],
  "textContent": "The Gobbler blog is powered by Astro. This website used to be powered by Ghost. But I like consistency, so this website is now powered by Astro as well.\n\nWith the help of Claude, all posts have been moved over while maintaining their original guids for RSS readers, all images downloaded from Ghost, all historical WordPress and Ghost HTML stripped, and all code blocks have been correctly indented 1.\n\nThis move means membership and newsletters have been retired. Everything that was previously gated by membership barriers is now available to all. For anyone concerned, Ghost will delete all data around the start of April. So sad.\n\nTangentially related to all this website work, I have discovered something new 2 (at least new to me): Universally Unique Lexicographically Sortable Identifiers (or ULIDs). They are kind of like UUIDs but sortable and decodable. In previous static sites where posts have had front matter, I've always used the post slug or a random UUID as a post identifer for feeds, but ULIDs seem to serve this purpose better. This post, for example, has a unique ID of 01KKC42KAQ8SWA4FTXVGWKJCKE and you can decode that at ulid.page.3 (On the new Gobbler blog I am using nanoIDs as post identifiers. It's nice to have options!)\n\nFor those that are interested, I create a new post by running npm run new \"Blog Title\", which runs this node scripts/new-post.mjs, which leads to this:\n\n#!/usr/bin/env node\n/**\n* Creates a new post with a ULID postId.\n*/\n\nimport { writeFile } from 'fs/promises';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { ulid } from 'ulid';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst POSTS_DIR = join(__dirname, '../src/content/posts');\n\nconst title = process.argv[2];\nif (!title) {\nconsole.error('Usage: node scripts/new-post.mjs \"Post Title\"');\nprocess.exit(1);\n}\n\nconst slug = title\n.toLowerCase()\n.replace(/[^\\w\\s-]/g, '')\n.replace(/\\s+/g, '-')\n.replace(/-+/g, '-')\n.trim();\n\nconst id = ulid();\nconst now = new Date();\nconst date = now.toISOString();\nconst datePrefix = now.toISOString().slice(0, 10);\nconst filePath = join(POSTS_DIR, `${datePrefix}-${slug}.md`);\n\nconst content = `---\ntitle: \"${title}\"\npostId: \"${id}\"\ndate: ${date}\ntags: []\nexcerpt: \"\"\n---\n\n`;\n\nawait writeFile(filePath, content);\nconsole.log(`Created: src/content/posts/${datePrefix}-${slug}.md`);\nconsole.log(`postId: ${id}`);\n\nMuch easier than manually crafting front matter.\n\nFootnotes\n\nThis took about an hour. ↩\n\nvia Jon Sterling's blog. ↩\n\nNice! And, if you can't tell, I'm excited to have footnotes working again. ↩",
  "title": "Hello, Astro"
}