Hands-On Astro Themes: Building Your MDX-Powered Blog from Scratch
How to create a blazing-fast blog using Astro with MDX, from zero setup to deploying with themes, layouts, and custom components.
If you’ve been juggling static site generators and wondering whether Astro is the real deal for your next blog, especially with MDX content, you’re in the right place. Astro is blazing fast by default, supports multiple frameworks beautifully, and when combined with MDX, it provides an elegant way to embed React (or any UI components) right inside your markdown. But setting up a blog with themes, layouts, and custom components can quickly get complex—especially if you want a maintainable, scalable foundation.
In this hands-on tutorial, I’ll walk you through how to build a performant Astro blog from scratch, powered with MDX, theming, and deploying it ready for production. We’ll talk about why Astro + MDX is a killer combo, how to structure your project, integrate React components in your MDX files, apply themes with reusable layouts, and finally, optimize and deploy your site. No fluff, just practical insights and tested code you can copy and adapt.
Why Choose Astro and MDX for Blogging?
First up — why should you bother with Astro and MDX? There are plenty of tools out there, so let’s be honest about trade-offs.
- Astro shines because it generates zero-JS by default on the client; your content is pre-rendered to static HTML, resulting in lightning-fast page loads. But if you want interactivity, it lets you sprinkle React/Vue/Svelte components where you want.
- MDX lets you write content in markdown but drop in React components seamlessly. That means your blog posts are more dynamic than “just markdown”: you can add interactive charts, previews, or custom widgets inline.
- Compared to something like Next.js with MDX, Astro’s partial hydration means better performance and simpler deployment (no Node.js server needed).
- Themes & layouts in Astro are straightforward and composable, letting you build a consistent design system without reinventing the wheel per project.
When to not use it: If you want extremely complex client-driven apps or need serverless backend logic tightly coupled with your blog, another tool might fit better. But for content-first, performant blogs or docs, Astro + MDX is simply hard to beat.
Setting Up an Astro Project with TypeScript
Let’s get practical. Here’s how to bootstrap an Astro blog project with TypeScript support.
- Create a new project:
npm create astro@latestYou’ll be prompted with options; for this tutorial:
- Choose
blogstarter (or an empty template if you prefer) - Enable TypeScript support
- Choose your preferred UI framework (React for this tutorial)
- Install dependencies
OR, if you want barebones:
mkdir astro-mdx-blog
cd astro-mdx-blog
npm init astro@latest -- --template basics- Add TypeScript if not included:
npm install --save-dev typescript
npx tsc --initAstro projects are happy with TypeScript; this keeps your components and config well-typed.
- Project Structure Snapshot:
astro-mdx-blog/
├── public/
├── src/
│ ├── components/
│ ├── layouts/
│ ├── pages/
│ └── content/
├── astro.config.mjs
├── package.json
├── tsconfig.json
└── ...Astro places pages inside /src/pages (auto routes), components in /src/components, and we’ll put MDX blog posts inside /src/content.
- Basic
astro.config.mjswith React support:
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
export default defineConfig({
integrations: [react()],
});That’s enough for a TypeScript Astro project ready for React components.
Adding MDX Support and Custom Components
To write blog posts with MDX and embed React components, you need to add MDX support to Astro.
Installing MDX Integration
npm install @astrojs/mdxAdd it to your config:
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import mdx from '@astrojs/mdx';
export default defineConfig({
integrations: [react(), mdx()],
markdown: {
syntaxHighlight: 'prism',
},
});This enables .mdx files anywhere in src/pages or as imported content.
Structuring Content with MDX
Create a folder for blog posts:
src/content/posts/Example MDX blog post (with React component):
---
title: "Hello Astro + MDX"
pubDate: "2025-12-18"
description: "Embedding React components in MDX content"
---
import { Counter } from '../../components/Counter.tsx';
# Welcome to my blog!
This is a markdown post with an embedded React component.
<Counter />React Counter Component Example
// Counter.tsx
import React, { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
);
}Loading MDX in Pages
Create /src/pages/blog/[slug].astro to load posts dynamically:
---
import { getCollection } from 'astro:content';
import BlogPostLayout from '../../layouts/BlogPostLayout.astro';
const { slug } = Astro.params;
const posts = await getCollection('posts');
const post = posts.find((p) => p.slug === slug);
if (!post) {
throw new Error(`Post not found: ${slug}`);
}
---
<BlogPostLayout {post}>
<post.Content />
</BlogPostLayout>For getCollection to work, configure Astro Content collections in src/content/config.ts:
import { defineCollection, z } from 'astro:content';
const blogCollection = defineCollection({
schema: z.object({
title: z.string(),
pubDate: z.string(),
description: z.string().optional(),
}),
});
export const collections = {
posts: blogCollection,
};Then update astro.config.mjs to point to src/content.
This approach provides type safety and content-driven routing.
Working with Astro Themes and Layout Patterns
Themes in Astro can feel less like black-box templates and more like composable layout systems that you can tweak.
Creating a Reusable BlogPostLayout
Example layout wrapping blog posts with metadata and styles:
---
const { title, pubDate, description } = Astro.props.post.data;
---
<html lang="en">
<head>
<title>{title}</title>
<meta name="description" content={description || ''} />
</head>
<body>
<article>
<header>
<h1>{title}</h1>
<time dateTime={pubDate}>{new Date(pubDate).toLocaleDateString()}</time>
</header>
<main>
{Astro.props.children}
</main>
</article>
</body>
</html>This keeps your post markup DRY and allows you to switch themes or tweak layout easily.
Adding Theme Support
You might want multiple themes (light/dark mode or entirely different looks). Here’s a minimal approach:
- Define CSS variables and classes for theme styles.
- Use an Astro component to toggle themes or set per-request theme.
Example theme provider (classic CSS variables + toggle logic left as an exercise):
---
const { theme = 'light' } = Astro.props;
---
<body class={theme}>
<slot />
</body>You could wrap your entire site with this provider.
Using Astro Integrations for Themes
There are community-powered Astro themes you can install and fork, but often starting with your custom theme (even minimal styles) reduces bloat and fits your needs better.
Deploying Your Blog to Production
Once your blog is ready, it’s time to deploy. Astro offers a great DX here.
Build Script with Optimizations
Add this to your package.json scripts:
{
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview"
}
}Run:
npm run buildAstro automatically does:
- SSR or static site generation (we’re using SSG here)
- CSS and JS minification
- Partial hydration only where needed (e.g., your Counter component)
- Image optimization (if configured)
Choosing a Host
Astro builds pure static files for blogs, making it perfect for:
- Netlify
- Vercel
- Cloudflare Pages
- GitHub Pages
- Your own static file host (e.g., S3 + CloudFront)
Example: Deploying to Vercel
- Push your repo to GitHub.
- Connect your repo in the Vercel dashboard.
- Let Vercel detect Astro; set the build command:
npm run buildand output directory:dist. - Deploy.
Your MDX blog will be blazing fast, SEO-friendly, and interactive where you embedded React components.
Final Thoughts
Astro + MDX is a compelling stack to build modern blogs that are fast without sacrificing interactivity. The key challenges are setting up content collections right, organizing your components and layouts for easy theming, and understanding Astro’s partial hydration model.
If you’re starting fresh, focus first on:
- Simple MDX blog posts with embedded React components
- A single clean layout component
- Minimal stateful UI sprinkled inside your posts
Then iterate on themes and deployment. Avoid loading big JS bundles upfront—Astro helps with that by default, but it’s easy to go back to bloated setups if you’re not careful.
Enjoy building your next blog with Astro and MDX!
Until next time, happy coding 👨💻
– Patricio Marroquin 💜
Related articles
Building a Rocket-Fuel Node.js API with TypeScript - Step by Step
A practical guide to crafting a scalable Node.js REST API using TypeScript, best architecture, and error handling that won’t make you cry.
Debugging Node.js Memory Leaks Step-by-Step (With Real Tools & Tips)
Memory leaks kill apps silently. I walk you through using tools like Chrome DevTools and heap snapshots to track down leaks in Node.js.
Building a scalable Redis cache layer for Node.js
Step-by-step guide to implement a Redis caching layer in Node.js apps that actually improves performance and avoids common pitfalls.