Skip to content
Astro as markdown 4 min read

Markdown in Astro

Markdown is the most natural way to write long-form content in Astro. A .md file is plain text with a lightweight syntax for headings, lists, links, and code, plus an optional block of YAML metadata at the top. Astro understands Markdown natively: it parses your files, renders them to semantic HTML, and ships zero JavaScript for the content itself. This makes Markdown ideal for blog posts, documentation, and any text-heavy page where speed and authoring comfort matter.

How Astro treats Markdown files

Astro processes Markdown in two distinct places, and knowing which one you are in determines how the file is used:

  • File-based pages — a .md file placed under src/pages/ becomes a route automatically. src/pages/about.md is served at /about, with no extra wiring.
  • Content collection entries — a .md file under src/content/ (or a configured content directory) is data, not a route. You query it with the Content Collections API and render it from an .astro page you control.

For anything beyond a one-off page, content collections are the recommended approach because they give you type-safe frontmatter and a single rendering layout.

Frontmatter

Frontmatter is a fenced block of YAML at the very top of the file, delimited by three dashes. It holds structured metadata — title, publish date, tags, draft status — that Astro parses separately from the body.

---
title: "Getting Started with Astro"
pubDate: 2026-06-14
author: "Ada Lovelace"
tags: ["astro", "ssg"]
draft: false
---

# Getting Started

Your first **Markdown** page renders as semantic HTML.

Quote any value that could be misread as a non-string — dates, numbers, and booleans you intend to keep as text. An unquoted 2026-06-14 parses as a date, while "2026-06-14" stays a string.

When a Markdown file lives in src/pages/, its frontmatter is exposed to a layout via the frontmatter prop. When it lives in a collection, the same data is validated against your schema and returned as entry.data.

Assigning a layout

A standalone Markdown page renders only the content’s HTML — no <html>, <head>, or site chrome. The layout frontmatter key points at an .astro component that wraps the rendered body and receives the frontmatter automatically.

---
// src/layouts/PostLayout.astro
const { frontmatter } = Astro.props;
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>{frontmatter.title}</title>
  </head>
  <body>
    <h1>{frontmatter.title}</h1>
    <p>By {frontmatter.author}</p>
    <article>
      <slot />
    </article>
  </body>
</html>
---
layout: ../layouts/PostLayout.astro
title: "Hello World"
author: "Grace Hopper"
---

This body is injected into the layout's `<slot />`.

The rendered Markdown is placed into the layout’s <slot />. Beyond frontmatter, the layout also receives helpful props such as url, file, and a headings array you can use to build a table of contents.

PropTypeDescription
frontmatterobjectAll parsed YAML frontmatter fields
urlstring | undefinedThe page’s public path (pages only)
filestringAbsolute path to the source .md file
headingsMarkdownHeading[]{ depth, slug, text } for each heading

Rendering collection entries

Collection Markdown carries no layout key. Instead you fetch the entry, call render(), and place the resulting <Content /> component inside your own page layout.

---
// src/pages/blog/[...slug].astro
import { getCollection, render } from 'astro:content';
import PostLayout from '../../layouts/PostLayout.astro';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { slug: post.id },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content, headings } = await render(post);
---
<PostLayout frontmatter={post.data}>
  <Content />
</PostLayout>

This pattern keeps your content portable: the same Markdown can be rendered, listed, filtered, or fed to an RSS feed without ever hard-coding a layout into the file.

Frontmatter access summary

// Pages: read via the layout prop
const { frontmatter } = Astro.props;

// Collections: read via the schema-validated data object
const { Content } = await render(post);
console.log(post.data.title);

Output:

Getting Started with Astro

Best practices

  • Reserve src/pages/*.md for true one-off pages; route everything repeatable through content collections for type safety.
  • Define a schema for every collection so missing or mistyped frontmatter fails at build time instead of in production.
  • Keep one canonical layout per content type and render collections through it rather than scattering layout: keys across files.
  • Quote ambiguous frontmatter values (dates, ISBNs, versions like "18") to prevent YAML from coercing them.
  • Use the headings array to generate tables of contents instead of re-parsing the body.
  • Reach for MDX only when a page genuinely needs interactive components — plain .md stays lighter and ships zero JavaScript.
Last updated June 14, 2026
Was this helpful?