Two Astro gotchas I hit building this very blog
I built this devlog on Astro content collections, and at first I wanted it in three locales — one markdown file per language. Wiring that up, I hit two traps worth writing down; neither fix is obvious.
Gotcha 1: a frontmatter `slug` *is* the entry id
I gave every post a `slug` field so the three language versions of the same story could share one URL:
lang: "en-us"
slug: "unify-mac-ios-downloads"
It built fine. But only the Chinese index listed the post — English and Japanese said "No posts yet."
The glob loader treats a `slug` frontmatter field as the entry **id**. All three files declared the *same* slug, so they collapsed into a single entry, and the last one loaded (`zh-tw`, alphabetically) won. The other two simply vanished from the collection — no error, no warning.
Fix: drop the `slug` field. Let the id be the file path (`en-us/unify-mac-ios-downloads`), which is unique per locale, and derive the URL slug from the filename instead:
const slug = post.id.replace(/^[^/]+\//, ''); // strip the locale folderGotcha 2: `getStaticPaths` runs in its own scope
With the ids fixed, the next build failed harder:
slugOf is not defined
I'd factored that little `replace` into a helper at the top of the route file, then called it inside `getStaticPaths`. But `getStaticPaths` is special — Astro extracts and runs it in isolation, so it **can't see other top-level consts** in the same frontmatter. The helper existed for the rest of the component; just not in there.
Fix: keep the derivation inline, or define it *inside* `getStaticPaths`:
export async function getStaticPaths() {
const posts = (await getCollection('blog')).filter((p) => p.data.lang === 'en-us');
return posts.map((post) => ({
params: { slug: post.id.replace(/^[^/]+\//, '') },
props: { post },
}));
}
Both are the kind of trap that builds green on the happy path and only bites once you have more than one entry, or share a helper. Now you know — the blog you're reading shipped right after.
Keep reading
-
grep cannot finish a domain migration
A hundred and six occurrences of the old host, replaced across thirty-two files. Certificates, redirects and configuration all moved. Then nobody could log in — because the value that broke was never the string anyone was searching for.
-
Spending cloud credit from a terminal
Anthropic gave every Claude Code account $250 of cloud-session credit. Spending it without burning the 16 GB machine on the desk turned into a division of labor: the terminal as dispatch desk, cloud sessions as the hands, the Air as the acceptance bench. Three hours of browser scripting before reading the first page of --help, then what the cloud cannot do, then a second account with nothing connected that found a real bug for two dollars.
-
Overrides on Overrides on Overrides
A ChatGPT-built portfolio site flashed on every load: a global style, a page style that overrode it with !important, and JavaScript that stamped classes on after load so the override could fire at all. Untangling it with agy surfaced Gemini's odd habits, a subagent that opened xkcd 353, and a lesson that measuring has to live in the system, not in the model's good intentions.