Blog

Writing posts

Abigail Chandler

This post is a file at content/posts/writing-posts.mdx. The file name is the URL, so it is served at /blog/writing-posts. Add a file, get a post. There is nothing to register and no index to update.

Every post starts with the block above:

export const meta = {
	title: "Writing posts",
	description: "One line. Used on the card, in search results, and on the social image.",
	date: "2026-03-12",
	published: true,
};

date is YYYY-MM-DD and sorts the archive newest first. Set published: false and the post disappears completely: off the index, out of the feed and the sitemap, and a 404 at its own URL. Run pnpm dev and flip it back to read a draft.

Code

Fenced blocks are highlighted at build time by Shiki, which means the colors are baked into the HTML. No highlighter ships to the browser, and nothing flashes gray before it paints:

type Post = {
	slug: string;
	title: string;
	date: string;
};

export function sortByDate(posts: Post[]): Post[] {
	return [...posts].sort((a, b) => b.date.localeCompare(a.date));
}

Name the language after the backticks. A block with no language still renders, just without color. Long lines scroll inside the block rather than stretching the page.

Both themes are highlighted from one pass, since the same markup carries light and dark colors, so switching themes does not re-render anything.

Tables and lists

GitHub-flavored markdown is on, so tables work:

FileWhat it holds
site.config.tsYour name, links, headline, About
content/projects.tsThe Projects section
cv.config.tsEverything printed on the PDF CV

So do task lists:

  • Write the post
  • Check it in both themes
  • Actually publish it

And ordinary ones:

  1. Create the file
  2. Fill in meta
  3. Write

Links and emphasis

Inline code, bold, italic, strikethrough, and links all behave. Block quotes are set off with a rule:

The nice thing about a blog you own is that nobody can put an interstitial on it.

Images

Put the file in public/ and reference it from the root:

![A build graph](/build-graph.png)

The alt text is not optional. It is what somebody using a screen reader gets instead of the picture, and what everyone gets when the image fails to load.

Changing how any of this looks

Every element above is rendered by a component in src/components/mdx-components.tsx, mapped by tag name. Want wider code blocks, or a different quote style? Edit the one entry. You are not fighting a theme.