How to Build a Blog with Markdown & Astro: Complete Step-by-Step Guide
Table of Contents
Why Astro for a Markdown Blog? Project Setup Astro Configuration Writing Content with Markdown Content Collections Frontmatter & Schema Dynamic Routes Blog Layout Component The [...slug].astro Route Creating the Blog List Page Deployment The Complete Workflow FAQYou have been writing Markdown. You have a collection of articles. Now you need a blog that is fast, flexible, and enjoyable to maintain.
Astro is the perfect companion for Markdown-powered sites. It treats Markdown files as first-class content sources, generates static HTML (no JavaScript runtime), and supports MDX for interactive components inside your articles. This is the exact stack powering MarkdownMaster's blog.
This guide walks you through every step — from zero to a deployed blog with Markdown content, automatic sitemaps, tag pages, and RSS feeds.
Before you start: Draft your articles in the MarkdownMaster online editor — live preview helps you get the formatting right before committing to your content files.
Why Astro for a Markdown Blog?
Astro is not the only static site generator, but it has three advantages that make it the best choice for Markdown blogs:
- Zero JavaScript by default. Astro renders everything to static HTML at build time. Your blog loads instantly, no React hydration, no client-side rendering, no wait times.
- Markdown as a first-class citizen. You can place
.mdfiles directly in yoursrc/pages/directory, and Astro turns them into pages automatically. No configuration needed for basic use. - Content Collections. Astro provides a structured way to manage blog posts with frontmatter validation, type-safe queries, and automatic sorting. This makes adding a new post as simple as creating a
.mdfile with the right frontmatter fields.
Other generators like Hugo or Jekyll are also good, but they require learning a templating language (Go templates or Liquid). Astro uses JavaScript or TypeScript throughout, which feels natural if you already work in the JS ecosystem.
Project Setup
Start a new Astro project with the blog template, or add blog support to an existing project:
# Create a new project
npm create astro@latest my-blog -- --template blog
# Or add to an existing project
npm install astro @astrojs/mdx @astrojs/rss @astrojs/sitemap
This installs Astro, MDX support (for interactive components inside Markdown), RSS generation, and automatic sitemap creation.
Astro Configuration
Configure astro.config.mjs to enable Markdown and the integrations you installed:
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://myblog.com',
integrations: [mdx(), sitemap()],
markdown: {
shikiConfig: {
theme: 'github-dark',
wrap: true,
},
},
}); The site property is required for sitemap generation. The shikiConfig enables syntax highlighting in your code blocks.
Writing Content with Markdown
With Astro, you have two options for writing blog content:
- Plain
.mdfiles — simple, no components inside articles, works with any Markdown editor .mdxfiles — allows importing and using Astro or React components inside your Markdown content
Plain Markdown is sufficient for most blogs. Start with .md and only switch to MDX when you need interactive elements inside articles.
Content Collections
Instead of scattering .md files across your pages directory, Astro's Content Collections keep posts organized:
src/content/
blog/
my-first-post.md
how-to-use-astro.md
markdown-tips.md
post-with-code-examples.md
Each file is a standard Markdown document with frontmatter:
---
title: 'My First Post'
date: 2026-04-29
tags: [markdown, astro, blog]
---
Welcome to my blog! To query these posts, you define a collection in your content configuration.
Frontmatter and Schema
Define the expected frontmatter fields in src/content/config.ts. This gives you type safety and validation:
import { defineCollection, z } from 'astro:content';
export const collections + {
blog: defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.date(),
tags: z.array(z.string()).optional(),
draft: z.boolean().optional(),
}),
}),
};
With this schema, Astro will validate every .md file against the schema and throw a build error if a post is missing required fields.
Pro tip: Add a
draft: truefield to your schema and filter drafts out in production builds. This lets you work on posts locally without publishing them.
Dynamic Routes
Instead of creating a separate page file for each blog post, use Astro's dynamic routing with a single [...slug].astro file.
Blog Layout Component
Create a layout that wraps each blog post. This handles the header, navigation, and footer consistently:
---
// src/layouts/BlogLayout.astro
export interface Props {
title: string;
description: string;
publishedDate: string;
tags?: string[];
}
const { title, description, publishedDate, tags } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<nav><a href="/">Home</a> › <a href="/blog/">Blog</a></nav>
<article>
<h1>{title}</h1>
<p class="meta">Published {publishedDate}</p>
<slot />
</article>
</body>
</html> The Astro.props brings in frontmatter data from each Markdown file. The <slot /> is where the rendered Markdown content appears.
The [...slug].astro Route
This single file handles all individual blog post pages:
---
import type { GetStaticPaths } from 'astro';
import { getCollection } from 'astro:content';
export const getStaticPaths + (async () => {
const blogPosts + await getCollection('blog');
return blogPosts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}) satisfies GetStaticPaths;
---
<!-- Astro renders the Markdown content inline here. -->
<!-- The page template wraps your content with layout and styles. -->
Astro generates a static HTML page for each post at build time. The slug from the filename becomes the URL.
Creating the Blog List Page
Create src/pages/blog/index.astro to list all posts with links:
---
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
const sortedPosts = posts
.filter(post => !post.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---
<h1>Blog</h1>
{posts.map(post => (
<article>
<h2><a href='/blog/example-post/'>{post.data.title}</a></h2>
<p>{post.data.description}</p>
<small>{post.data.date.toLocaleDateString()}</small>
</article>
))} This gives you a dynamic index that automatically updates when you add new posts.
Deployment
Astro builds to static HTML. Deploy anywhere that serves static files:
| Platform | Deploy Method | Notes |
|---|---|---|
| Cloudflare Pages | Git push + wrangler | Free tier, global CDN |
| Netlify | Git push to main | Auto-detects Astro |
| Vercel | Git push to main | Best for serverless API |
| GitHub Pages | Actions + gh-pages | Free, needs CI setup |
For Cloudflare Pages, set the build command to npm run build, output directory to dist, and connect your Git repository.
The Complete Workflow
Here is the end-to-end workflow for maintaining an Astro Markdown blog:
- Draft new articles in a Markdown editor like MarkdownMaster with live preview
- Create a new
.mdfile insrc/content/blog/with proper frontmatter - Preview locally with
npm run dev— hot reload shows changes instantly - Format code blocks using triple backticks with language tags
- Link between posts using relative Markdown links. Learn more about Markdown links
- Deploy — push to Git, your CI handles the rest
Once the infrastructure is set up, publishing a new post is just: write, commit, push.
FAQ
Can I use other static site generators with Markdown?
Yes — Hugo, Jekyll, Next.js, and Eleventy all support Markdown. Astro is the best choice if you want zero-JS output and native Markdown support without a complex pipeline.
Should I use .md or .mdx files?
Start with .md. Only switch to .mdx when you need interactive components inside your articles. Most blogs never need MDX.
How do I add an RSS feed?
Use the @astrojs/rss integration. Create a src/pages/rss.xml.js file that generates RSS from your content collection.
Does Astro support tags and categories?
Yes. Add a tags field to your frontmatter schema, then create a dynamic route like /blog/tag/[tag].astro that queries posts by tag.
How many blog posts can Astro handle?
Astro builds static HTML. Sites with thousands of posts build in under a minute. Performance is flat regardless of content volume.
What about images in blog posts?
Store images in src/assets/ or public/ and reference them with relative Markdown image syntax.