Writing Blog Posts with MDX

How to write posts using frontmatter, markdown, and custom Astro components — with real examples for every field.

Writing Blog Posts with MDX
Table of contents

Every blog post is an .mdx file inside src/data/blog/. MDX is Markdown with the ability to import and use Astro (or React) components directly in your prose. You get the simplicity of Markdown with the flexibility of components when you need them.

Creating a new post

The fastest way is the admin panel at http://localhost:4321/admin/. Click + New, enter a title, and the slug is auto-generated. You’ll land straight in the editor.

Alternatively, create a file manually:

Terminal window
touch src/data/blog/my-new-post.mdx
i

Slug = filename

The filename (without .mdx) becomes the URL slug. my-new-post.mdx/posts/my-new-post/. Keep slugs lowercase, hyphen-separated, and descriptive.

Frontmatter

Every post starts with a YAML frontmatter block between --- delimiters:

---
title: My Post Title
description: One or two sentences shown in search results and post cards.
pubDate: 2026-04-24
updatedDate: 2026-05-01
author: Jane Doe
tags:
- tutorial
- web
heroImage: /images/blog/my-post/hero.webp
draft: false
featured: false
---
FieldRequiredNotes
titleMax 60 chars for good SEO
descriptionMax 160 chars, shown in cards and search
pubDateYYYY-MM-DD format
updatedDateShows “Updated” label in the layout
authorDefaults to SITE.author from config
tagsArray of strings, used for tag pages
heroImagePath to cover image (shown in cards and post header)
drafttrue hides the post from production builds
featuredtrue shows the post in the featured strip on the home page

Markdown basics

Standard CommonMark markdown works everywhere:

## Heading level 2
Regular paragraph with **bold**, _italic_, and `inline code`.
- Bullet list
- Another item
1. Numbered list
2. Second item
> Blockquote for longer citations.
[Link text](https://example.com)
![Image alt text](/images/my-image.png)
!

Use H2 and below

The post title is rendered as <h1> by the layout. Start your headings at ## to maintain the correct document outline.

Code blocks

Wrap code in triple backticks with a language identifier. Syntax highlighting is handled by Expressive Code with a Shiki backend:

```ts
function greet(name: string): string {
return `Hello, ${name}`;
}
```

Supported features: line numbers, diff highlighting, titles, and more — see the Expressive Code docs.

Importing custom components

After the frontmatter, import any component you want to use:

import Callout from "@/components/blog/Callout.astro";
import Steps from "@/components/blog/Steps.astro";
<Callout type="success" title="Works!">
This is a custom component inside MDX.
</Callout>

All available components are documented in the Component Reference post.

Images

Put images in public/images/blog/your-slug/ and reference them with a root-relative path:

import Figure from "@/components/blog/Figure.astro";
<Figure
src="/images/blog/my-post/screenshot.webp"
alt="Descriptive alt text"
caption="Optional caption shown below the image."
/>
TODO: screenshot of a Figure component rendered in a blog post
The Figure component renders a bordered image with an optional caption.
i

Alt text matters

Always write descriptive alt text. It’s read by screen readers and displayed when images fail to load.

Keyboard shortcuts in the editor

When writing in the admin panel:

  • + S — save (autosave also runs after 900ms of inactivity)
  • Switch to the Frontmatter tab to edit metadata without touching YAML by hand
  • The Components tab in the right panel shows a searchable list with insert snippets
  • The Images tab lets you upload and copy image URLs directly

Drafts and scheduling

Set draft: true to hide a post from production. It still appears in the admin panel and in bun run dev.

To schedule a post, set pubDate to a future date — it will be hidden until that date is reached (within the grace window configured in site.config.ts).

How the grace window works

scheduledPostGraceMs in site.config.ts defaults to 0. If you set it to 3600000 (1 hour), posts will appear up to 1 hour before their pubDate. Useful for deployment lag.

Tags

Tags are free-form strings. They generate tag pages at /tags/your-tag/ automatically. Use consistent, lowercase, hyphenated slugs:

tags:
- web-development
- typescript
- tutorial
  • src/
    • pages/
      • tags/
        • [tag]/
          • [...page].astro

Frontmatter tips

Title length

Keep titles under 60 characters for Google — the SEO preview in the Frontmatter tab shows a live meter.

!

Description length

Descriptions over 160 characters are truncated in search results. The meter turns orange around 130 and red at 160.