In this tutorial, we will build a simple blog using Astro, microCMS, and Vercel.
Astro is a modern web framework designed for fast, content-focused websites. It is especially well suited for blogs, documentation sites, landing pages, and corporate websites where most of the content can be rendered as static HTML.
microCMS is a headless CMS that lets you manage content through APIs. By combining Astro with microCMS, you can keep your frontend fast while allowing editors and content teams to manage blog posts from a user-friendly CMS dashboard.
Finally, we will deploy the blog to Vercel.
By the end of this guide, you will have a blog that:
- Fetches blog posts from microCMS
- Uses Astro Content Loader to manage external content
- Displays a blog post list
- Generates detail pages for each blog post
- Deploys to Vercel
What is Astro?
Astro is a web framework for building fast websites.
One of Astro’s key ideas is to send as little JavaScript to the browser as possible. Instead of turning every page into a JavaScript-heavy application, Astro renders HTML on the server by default.
Astro is also known for its islands architecture. This allows you to keep most of the page static while adding interactivity only where it is needed. You can use UI frameworks such as React, Vue, Svelte, Preact, Solid, and others for interactive components.
Preparation
In this article, we will use the following versions. Some features may differ slightly depending on the version.
- Astro 6.3.8
- microcms-js-sdk 3.4.0
Create a New Astro Project
Now, let’s create an Astro project.
Open your terminal and run the following command.
npm create astro@latest
Follow the prompts in your terminal.
Open http://localhost:4321/ in your browser, and you should see the Astro welcome page.
This time, select Create your own.
Then, the service information input screen will appear. Enter astro-microcms-blog as the service name, and enter any value you like for the service ID.
Setting Environment Variables
First, we will set environment variables to safely manage values such as the microCMS API key.
Create a new .env file in the root directory of your project and enter the following information.
MICROCMS_API_KEY=<YOUR_KEY_VALUE>
MICROCMS_SERVICE_DOMAIN=<YOUR_SERVICE_DOMAIN>
MICROCMS_API_KEY: You can check this from Service Settings > API Key in the microCMS admin dashboard.
MICROCMS_SERVICE_DOMAIN: This is the service ID you created earlier. You can also check it from the microCMS admin dashboard URL. Inhttps://xxxxxxxx.microcms.io, thexxxxxxxxpart is your service domain.
Installing the microCMS JavaScript SDK
Next, install the
Enter the following values for API Name and Endpoint.
Prepare the following fields for the API schema.
- Title
- Description
- Hero Image
- Body
Turn on the Required field switch for all fields.
The fields you defined earlier will be displayed, so enter some sample content.
Preparing the Content Loader
Astro has a feature called Content Collections, which allows you to manage local files such as Markdown as type-safe content.
To make external data compatible with Content Collections, you need to introduce a dedicated loader.
In this step, we will add a loader to read content managed in microCMS.
First, create src/content.config.ts and add the implementation of a Content Loader to read data from microCMS.
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod';
import type { Loader } from 'astro/loaders';
import { createClient } from 'microcms-js-sdk';
function getClient() {
return createClient({
serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
apiKey: import.meta.env.MICROCMS_API_KEY,
});
}
const microCMSImageSchema = z.object({
url: z.string(),
width: z.number().optional(),
height: z.number().optional(),
alt: z.string().optional(),
});
const microCMSDateFields = {
createdAt: z.string(),
updatedAt: z.string(),
publishedAt: z.string(),
revisedAt: z.string(),
};
function microCMSLoader(endpoint: string): Loader {
return {
name: `microcms-${endpoint}`,
load: async ({ store, parseData, logger }) => {
try {
logger.info(`Fetching ${endpoint} data from microCMS...`);
const contents = await getClient().getAllContents({ endpoint });
store.clear();
for (const content of contents) {
const { id, ...fields } = content;
const data = await parseData({ id, data: fields });
store.set({
id,
data,
rendered: {
html: data.body,
},
});
}
logger.info(`Fetched ${contents.length} ${endpoint} items`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error(`Failed to fetch ${endpoint} from microCMS: ${message}`);
}
},
};
}
const blog = defineCollection({
loader: microCMSLoader('blog'),
schema: z.object({
title: z.string(),
description: z.string(),
heroImage: microCMSImageSchema,
body: z.string(),
...microCMSDateFields,
}),
});
export const collections = {
blog,
};
Now you are ready to call microCMS content in a type-safe way from anywhere.
Displaying the Article List
Now that the preparation is complete, let’s start writing the Astro source code.
This time, we will assume that the blog article list is displayed on the top page.
First, replace the contents of src/pages/index.astro with the following.
This file is called when you access the root path, http://localhost:4321/.
---
import Layout from '../layouts/Layout.astro';
import { getCollection } from "astro:content";
const posts = await getCollection("blog");
---
<Layout>
<main>
<ul>
{posts.map((post) => (
<li>
<a href={post.id}>{post.data.title}</a>
</li>
))}
</ul>
</main>
</Layout>
The --- at the beginning is called a code fence.
Inside the code fence, you can write JavaScript logic.
By default, this part is executed on the server side at build time, and the JavaScript code does not remain in the built site.
In this section, we fetch the list of articles from microCMS and display them on the screen as list elements.
With this setup, the browser display will look like this.
We were able to display a list of articles fetched from microCMS.
Creating the Blog Article Detail Page
At this point, we have not created a detail page yet.
So, if you click a link, you should see a 404 page like the one below.
Let’s create a detail page so that it does not result in a 404.
This time, create a file named src/pages/[postId].astro so that pages can be accessed with paths such as http://localhost:4321/post-id.
Write the following code.
---
import Layout from "../layouts/Layout.astro";
import { getCollection } from "astro:content";
// Get all paths for article detail pages
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map((post) => ({
params: {
postId: post.id,
},
props: {
post,
},
}));
}
// Get article detail information
const { post } = Astro.props;
---
<Layout title={post.data.title} description={post.data.description}>
<main>
<h1 class="title">{post.data.title}</h1>
<p class="publishedAt">Published at: {post.data.publishedAt}</p>
<img src={post.data.heroImage.url} alt={post.data.heroImage.alt} />
<div class="post" set:html={post.data.body}></div>
</main>
</Layout>
<style>
main {
margin: auto;
padding: 1em;
max-width: 60ch;
}
img {
width: 100%;
height: auto;
}
</style>
getStaticPaths is used to specify what values this dynamic path can take.
Also, since the rich editor in microCMS returns an HTML string as-is, we use set:html to render the HTML directly on the screen.
<div class="post">{post.data.body}</div>
If you write it like the example above, the HTML part will be escaped and displayed as plain text on the screen, so please be careful.
The related documentation is as follows.
I will explain later why we pass title and description as properties to the <Layout> tag.
Once you have completed the code above, the detail page will also be displayed.
Try changing the submitted content in microCMS and confirm that the article display also changes.
Preparing a GitHub Repository
If you connect GitHub to your hosting service, builds and deployments can run automatically whenever you update the source code.
First, let’s prepare a GitHub repository.
Go to GitHub and select New repository from the + button in the header.
Enter astro-microcms-blog as the repository name, and set the visibility to Private.
If you want to publish the source code as well, Public is also fine.
Then click Deploy.
If the deployment succeeds, you will see a screen like the one below.
Next, set the issued Webhook in the microCMS admin dashboard.
Open API Settings > Webhook for the blog you created earlier.
Click the + Add button and select Vercel.
Now, when you update an article from microCMS, a Vercel build will start via the Webhook, and the content will be reflected in the production environment.
This completes the so-called Jamstack architecture.
Conclusion
In this article, we built a blog using Astro and microCMS.
By combining Astro’s fast static site generation with microCMS as a headless CMS, we were able to create a blog where content can be managed from an intuitive admin dashboard while still keeping the frontend simple and highly performant.
We also covered the full flow from setting up an Astro project, creating a microCMS API, fetching content with Astro Content Collections, generating article detail pages with dynamic routing, and finally deploying the site to Vercel. By connecting microCMS and Vercel with a Webhook, published content can automatically trigger a new deployment, making the site easier to maintain in a real-world workflow.
This setup is a good starting point for many types of content-driven websites, not only blogs but also documentation sites, news pages, landing pages, and portfolios.
From here, you can continue improving the site by adding styles, categories, tags, pagination, SEO settings, preview functionality, or search features.
Have fun customizing your blog and building your own content-driven website with Astro and microCMS!

SOCIAL SHARE CARD GENERATOR