Markdown Tips MarkdownMaster Team

How to Build a Blog with Markdown & Astro: Complete Step-by-Step Guide

You 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:

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:

  1. Plain .md files — simple, no components inside articles, works with any Markdown editor
  2. .mdx files — 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: true field 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:

PlatformDeploy MethodNotes
Cloudflare PagesGit push + wranglerFree tier, global CDN
NetlifyGit push to mainAuto-detects Astro
VercelGit push to mainBest for serverless API
GitHub PagesActions + gh-pagesFree, 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:

  1. Draft new articles in a Markdown editor like MarkdownMaster with live preview
  2. Create a new .md file in src/content/blog/ with proper frontmatter
  3. Preview locally with npm run dev — hot reload shows changes instantly
  4. Format code blocks using triple backticks with language tags
  5. Link between posts using relative Markdown links. Learn more about Markdown links
  6. 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.