Building a Blog with Next.js and microCMS: Content Management and Display with a Headless CMS

  • nextjs
    nextjs
  • vercel
    vercel
Published on 2024/12/16

Introduction

Let’s build a simple blog site using Next.js and a headless CMS.

There are many headless CMS services available, but this time we’ll use microCMS, a service from Japan. (Naturally, it has extensive Japanese documentation.)

The official documentation already summarizes how to get started, so we’ll follow that as a base and add explanations where needed while we work through it.

You can find the microCMS tutorial here:

https://blog.microcms.io/microcms-next15-jamstack-blog/

Goal of this tutorial

First, we’ll create a sample blog in the microCMS management console.

Image from Gyazo

Then we’ll implement a Next.js app that accesses microCMS, fetches blog information, and displays it.

Image from Gyazo

We’ll configure a Webhook so that every time a blog is added or updated, it’s automatically reflected in Next.js.
We’ll go as far as a setup that can be used in a real production environment, so I hope this article helps you build a blog site or run a website.

What is microCMS?

  • Headless CMS
    A headless CMS is a CMS where the backend (content management) and frontend (presentation) are separated.
    Since microCMS provides content via an API, you can freely build the frontend with your favorite framework or platform such as React, Next.js, or Vue.js.

  • No-code management screen builder
    You can create content management screens without writing code. You can freely customize content types (blog posts, product information, FAQs, etc.).
    You can configure fields (text, images, rich text, etc.) via an intuitive UI.

  • API-first design
    You can easily fetch content using REST API or GraphQL API.
    It works well with serverless architectures and Jamstack, supporting modern development practices.

  • Scalable and reliable
    You don’t have to worry about server scaling, and it can handle large amounts of traffic.
    The admin console is secure and supports multi-user operation without issues.

  • A service from Japan
    microCMS is a Japanese service, with rich Japanese-language support and features tailored to the Japanese market.

System architecture

microCMS acts as the backend and handles content management.

Blog authors post articles from the blog editing screen provided by microCMS.

microCMS also provides an API, and Next.js accesses it via Web API to fetch blog information, which users then view.

In this tutorial, we’ll run Next.js on Vercel and verify that we can actually access it using the domain issued by Vercel.

Image from Gyazo

Preparation

Register a microCMS account and create a service

First, register an account to use microCMS and create a service where you’ll manage content.

For details, please refer to the official documentation:

https://blog.microcms.io/getting-started/

That document also covers API creation, but from the API creation step onward, we’ll explain it in this article, so going as far as service creation is enough for now.

Also, within the scope of this tutorial, no costs will be incurred, so don’t worry.

Create an API in microCMS

You should now have completed service creation from the previous steps.

Image from Gyazo

Create the Category API

  1. Click “Content (API)” in the left menu to open the API creation menu, then select “Create from scratch”.

Image from Gyazo

  1. Enter the API name and endpoint.

API name: Category
Endpoint: categories

The endpoint will be the URL that Next.js accesses.

Image from Gyazo

  1. Decide what data this API will return.

This time we’ll return a list of categories, so choose “List” format.

Image from Gyazo

Field ID: name
Display name: Field name
Type: Text field

Image from Gyazo

That completes creation of the Category API.

At this point, there is still no category data at all.

That would be inconvenient when fetching data, so click “Add” and create some dummy data.

Image from Gyazo

I added about three entries.

Image from Gyazo

By the way, there’s an “API preview” button in the top right. If you click it, you’ll see sample code for fetching data from this API in various languages.

Try selecting JavaScript and clicking “Get”; you’ll see a response.

Image from Gyazo

You can see that the values you added are set under the name key.

You can also confirm that information such as total count and offset is returned separately from contents.

Being able to inspect the response before actually integrating data fetching makes development easier. Very handy.

Create the Blog API

Again, from “Content (API)” in the left menu, open the API creation menu and select “Create from scratch”.

  1. Enter the API name and endpoint.

API name: Blog
Endpoint: blogs

Note: This endpoint differs from the one in the official microCMS tutorial, but you can choose whatever you like.

  1. Decide what data this API will return.

This time we’ll return a list of blog posts, so choose “List” format.

Image from Gyazo
Let’s briefly explain.

For the field with ID body, where we set the article body, we choose the type “Rich editor”.

The rich editor allows not only text but also various formatting options to make the blog easier to read, such as text color, indentation, quotes, and tables.

In practice, HTML is stored and passed through the API.

Next.js will read the HTML it receives and display it as a blog post.

We’ll explain the conversion process in more detail later when we configure Next.js.

We also set a field ID category, which will reference the “Category” entries we created earlier.

That completes creation of the Blog API.

Again, at this point there is no blog data yet, so click “Add” and create a sample blog post.

Image from Gyazo

Create some blog posts with any content you like.

There’s a category selection field below the body, so assign the categories you created earlier there.

Click “Publish” in the top right to save it.
You can also click “Save as draft” to save, but note that draft posts cannot be fetched via the API.

Image from Gyazo

One post is a bit sparse, so create three.

Image from Gyazo

You can also preview the API response for blogs via API preview.

For the third blog post, if you look at body, you'll see a <p> tag outputting "Test".

We’ve confirmed that HTML is being returned in the response.

You can also see that inside category there is a name with cooking set, confirming that the category used to classify the blog is included.

Image from Gyazo

Now that we’ve created sample data, let’s move on.

Create a Next.js project

Our blog is ready, so let’s prepare Next.js to display it.

Create a project from the command line:

npx create-next-app@latest nextjs-microcms-blog-tutorial

Image from Gyazo

Check that it starts up:

npm run dev

Image from Gyazo

As a side note, from Next.js 15, Turbopack has been released as stable, which seems to speed up initial startup and refresh after code changes.

Now that you mention it, it does feel faster… or maybe not so much…

If you’re interested, see this article:

https://nextjs.org/blog/turbopack-for-development-stable

Connect Next.js and microCMS

This is the main theme of this article.

We’ll discuss how to use the APIs created in microCMS to fetch blog information.

Get the API key

In microCMS, you include an API key in your request to access specific data.

Even though the API is “public”, that doesn’t mean anyone can use it; access is restricted.

Go back to microCMS and obtain two pieces of information.

MICROCMS_API_KEY

You can find this in the microCMS admin under “Service settings > API keys”.

Image from Gyazo

MICROCMS_SERVICE_DOMAIN

This is the “example” part of the microCMS admin URL (https://example.microcms.io).

In my sample service, tdipisxadb corresponds to this.

Image from Gyazo

Configure the env file

Create .env.development.local in the root of your Next.js project and set the values you just obtained:

MICROCMS_API_KEY=xxxxxxxx
MICROCMS_SERVICE_DOMAIN=xxxxxxxx

Replace xxxxxxxx with the values for your own service.

Install the SDK

We’ll use the official SDK provided by microCMS to connect it with Next.js.

The README explains how to use the SDK in detail, so here’s the link:

https://github.com/microcmsio/microcms-js-sdk

Install it:

npm install microcms-js-sdk

Create shared code to initialize the SDK

We’ll use the SDK we just installed to connect, and since we’ll access the API in various places, we’ll centralize the common logic.

Create libs/microcms.ts and add the following code:

microcms.ts
// libs/microcms.ts
import { createClient } from 'microcms-js-sdk';

// Throw an error if MICROCMS_SERVICE_DOMAIN is not set in environment variables
if (!process.env.MICROCMS_SERVICE_DOMAIN) {
  throw new Error('MICROCMS_SERVICE_DOMAIN is required');
}

// Throw an error if MICROCMS_API_KEY is not set in environment variables
if (!process.env.MICROCMS_API_KEY) {
  throw new Error('MICROCMS_API_KEY is required');
}

// Initialize the Client SDK
export const client = createClient({
  serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: process.env.MICROCMS_API_KEY,
});

Explanation of the code:

// Throw an error if MICROCMS_SERVICE_DOMAIN is not set in environment variables
if (!process.env.MICROCMS_SERVICE_DOMAIN) {
  throw new Error('MICROCMS_SERVICE_DOMAIN is required');
}

// Throw an error if MICROCMS_API_KEY is not set in environment variables
if (!process.env.MICROCMS_API_KEY) {
  throw new Error('MICROCMS_API_KEY is required');
}

This checks whether the environment variables are set.

If they aren’t set yet, it throws an error.

// Initialize the Client SDK
export const client = createClient({
  serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: process.env.MICROCMS_API_KEY,
});

This initializes the SDK using the environment variables as arguments.

We’ll use this client to fetch data from microCMS.

Fetch and display the blog list in Next.js

Edit app/page.tsx, which corresponds to the Next.js top page, with the following code.

You can delete all the sample code created with the project.

page.tsx
import Link from 'next/link';
import { client } from '../libs/microcms';

type Props = {
  id: string;
  title: string;
};

// Fetch blog posts from microCMS
async function getBlogPosts(): Promise<Props[]> {
  const data = await client.get({
    endpoint: 'blogs', // 'blogs' is the microCMS endpoint name
    queries: {
      fields: 'id,title',  // Fetch id and title
      limit: 5,  // Fetch the latest 5 posts
    },
  });
  return data.contents;
}
export default async function Home() {
  const posts = await getBlogPosts();

  return (
    <main className='p-4'>
      <h1>Blog Posts List</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <Link href={`/blog/${post.id}`}> {/* Generate link to the post */}
              {post.title} {/* Display title */}
            </Link>
          </li>
        ))}
      </ul>
    </main>
  );
}

We use getBlogPosts to fetch data on the server side.

The fetched blog list, posts, is expanded with map and we display the blog titles.

We also use Next.js’s Link, so clicking a title navigates to /blog/${post.id}.

This destination is intended to display the blog details.

We haven’t created the destination page yet, so clicking a title will show a 404 for now.

If you see the blog titles displayed like this, you’re good.

We’ve successfully accessed microCMS from Next.js and displayed the blog list.

Image from Gyazo

Display blog details in Next.js

To display blog details, create a new page.

Create app/blogs/[id]/page.tsx and add the following code:

page.tsx
import { client } from '../../../libs/microcms';

// Type definition for blog post
type Props = {
  id: string;
  title: string;
  body: string;
  publishedAt: string;
  category: { name: string };
};

// Fetch specific blog post from microCMS
async function getBlogPost(id: string) {
  const data: Props = await client.get({
    endpoint: 'blogs',
    contentId: id,
  });

  return data;
}

// Generate blog post detail page
export default async function BlogPostPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const post = await getBlogPost(id);


  const formattedDate = post.publishedAt.slice(0, 10); // Format date

  return (
    <main className='p-4'>
      <h1>{post.title}</h1> {/* Display title */}
      <div>{formattedDate}</div> {/* Display date */}
      <div>Category: {post.category && post.category.name}</div> {/* Display category */}
      <div dangerouslySetInnerHTML={{ __html: post.body }} /> {/* Display post content */}
    </main>
  );
}

// Generate static paths
export async function generateStaticParams() {
  const contentIds = await client.getAllContentIds({ endpoint: 'blogs' });

  return contentIds.map((contentId) => ({
    id: contentId,
  }));
}

Let’s go through the code.

This is the server-side logic to fetch a specific article from microCMS.
By setting contentId in the get method, you can fetch only the blog post with that id.

async function getBlogPost(id: string) {
  const data: Props = await client.get({
    endpoint: 'blogs',
    contentId: id,
  });

  return data;
}

To fetch blog posts with the get method, you can also use options like queries in addition to endpoint and contentId.

For details, see the documentation below:

https://github.com/microcmsio/microcms-js-sdk?tab=readme-ov-file#get-content-list

export default async function BlogPostPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const post = await getBlogPost(id);


  const formattedDate = post.publishedAt.slice(0, 10); // Format date

  return (
    <main>
      <h1>{post.title}</h1> {/* Display title */}
      <div>{formattedDate}</div> {/* Display date */}
      <div>Category: {post.category && post.category.name}</div> {/* Display category */}
      <div dangerouslySetInnerHTML={{ __html: post.body }} /> {/* Display post content */}
    </main>
  );
}

This uses the fetch logic above to get the blog post and then renders it.

The target ID comes from the [id] in the path.

For the body field, which contains HTML, we use dangerouslySetInnerHTML to display it.

dangerouslySetInnerHTML is a React property used to insert HTML directly into the DOM.

Using this, you can render HTML provided as a string as-is.

Normally, React safely updates the DOM using JSX, but this property bypasses that mechanism and inserts arbitrary HTML.

Since we’re using microCMS’s rich editor to create the blog body, and it doesn’t just embed arbitrary HTML, it’s fine to display it with dangerouslySetInnerHTML.

However, if you display arbitrary HTML with dangerouslySetInnerHTML, there’s a risk of injection. In that case, it’s better to sanitize it (remove unwanted scripts, etc.) using a parser like html-react-parser before rendering.

export async function generateStaticParams() {
  const contentIds = await client.getAllContentIds({ endpoint: 'blogs' });

  return contentIds.map((contentId) => ({
    id: contentId,
  }));
}

generateStaticParams is a function provided by Next.js.

Used together with dynamic route segments, it allows you to statically generate routes at build time.

It runs at build time, and Next.js uses the returned list to generate static pages.

Compared to dynamically generated pages, the response is faster.

For pages whose content must change based on user requests, such as search pages or “My Page” dashboards, you need dynamic generation, but for a blog service, pre-generating pages is recommended.

After writing the code, click a link from the top page and you should see the blog details.

Image from Gyazo

You can confirm in the Elements panel on the right that the HTML sent from microCMS is being rendered.

However, since no CSS is applied, the display looks like plain text lined up.
For example, lists and H1 headings will be much easier to read with CSS, so let’s address that.

Style the blog details with CSS

There are several ways to apply CSS in Next.js.

Tailwind CSS is available by default and is recommended when writing JSX, but we can’t use Tailwind CSS directly on the HTML coming from microCMS.

This time, we’ll use CSS Modules to apply CSS to the HTML we receive.

CSS Modules let you scope styles (apply CSS only to specific components).

So, for example, if you set the font size of H1 tags to 32px, it will apply only to H1 tags in the blog detail area, not to all H1 tags across the site. That means you can define CSS without worrying about the entire site.

Add CSS to app/blog/[id]/page.module.css:

page.module.css
.main {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem 1rem;
  background-color: white;
}

.title {
  font-size: 2.5rem;
  font-weight: bold;
  color: #222;
  margin: 1rem 0;
  text-align: center;
  line-height: 1.2;
}

.date {
  font-size: 0.875rem;
  color: #888;
  text-align: center;
  margin-bottom: 0.5rem;
}

.category {
  font-size: 0.875rem;
  text-align: center;
  margin-bottom: 2rem;
  color: #888;
}

.post h1 {
  font-size: 2.5rem;
  font-weight: bold;
  color: #333;
  margin: 2rem 0 1rem;
  line-height: 1.2;
  border-bottom: 2px solid #ddd;
  padding-bottom: 0.5rem;
}

.post h2 {
  font-size: 2rem;
  font-weight: bold;
  color: #444;
  margin: 1.5rem 0 0.75rem;
  line-height: 1.3;
  border-bottom: 1px solid #ddd;
  padding-bottom: 0.25rem;
}

.post p {
  font-size: 1rem;
  line-height: 1.6;
  color: #555;
  margin: 1rem 0;
}

.post ul {
  font-size: 1rem;
  line-height: 1.6;
  color: #555;
  margin: 1rem 0 1rem 1.5rem;
  padding-left: 1.5rem;
  list-style-type: disc;
}

.post ul li {
  margin-bottom: 0.5rem;
}

.post table {
  width: 100%;
  border-collapse: collapse;
  margin: 20px 0;
}

.post th {
  padding: 10px;
  text-align: left;
  border: 1px solid #ddd;
  background-color: #f4f4f4;
  font-weight: bold;
}

.post td {
  padding: 10px;
  text-align: right;
  border: 1px solid #ddd;
}

After creating the CSS, apply it to the blog details.

Edit app/blogs/[id]/page.tsx:

page.tsx
+ import styles from './page.module.css';

(※ Partially omitted)

export default async function BlogPostPage({
  params: { id },
}: {
  params: { id: string }
}) {
  const post = await getBlogPost(id);

  const formattedDate = post.publishedAt.slice(0, 10); // Format date

  return (
-     <main>
+     <main className={styles.main}>
-       <h1>{post.title}</h1> {/* Display title */}
+       <h1 className={styles.title}>{post.title}</h1> {/* Display title */}
-       <div>{formattedDate}</div> {/* Display date */}
+       <div className={styles.date}>{formattedDate}</div> {/* Display date */}
-       <div>Category: {post.category && post.category.name}</div> {/* Display category */}
+       <div className={styles.category}>Category: {post.category && post.category.name}</div> {/* Display category */}
-       <div dangerouslySetInnerHTML={{ __html: post.body }} /> {/* Display post content */}
+       <div className={styles.post} dangerouslySetInnerHTML={{ __html: post.body }} /> {/* Display post content */}
    </main>
  );
}

Import the css and apply it to each div.

Once applied, check the blog details again.

Image from Gyazo

We also added CSS for tables since we created one.

Image from Gyazo

We’ll skip the finer points of the CSS.

For now, I hope it’s clear that you can style the HTML sent from microCMS using CSS Modules.

Build Next.js

So far, we’ve been running in development mode and testing locally.

We want to confirm that we can run Next.js on Vercel, so let’s build locally once and check for errors.

npm run build

Image from Gyazo

We got an error.

Because environment variables in .env.development.local aren’t loaded during build, copy it to .env.local.

Now the build succeeds.

Image from Gyazo

Deploy to Vercel

Let’s deploy Next.js to Vercel and confirm we can access it.

Vercel is the company behind Next.js and provides an environment where you can easily deploy Next.js.

Register a Vercel account

It’s free. You can sign up here:

https://vercel.com/

Connect with GitHub

By connecting with GitHub, you can deploy just by selecting a repository.

Choose “Add New...” -> “Project”.

Image from Gyazo

Find the target repository from your connected GitHub account and click “Import”.

Image from Gyazo

Here, configure environment variables.

Set MICROCMS_API_KEY and MICROCMS_SERVICE_DOMAIN in “Environment Variables”, which you had set in .env.XXX files.

.env files are excluded from GitHub, so you configure them directly in Vercel.
Click “Deploy” to start the deployment process.

Image from Gyazo

Deployment is in progress.

Image from Gyazo

Deployment is complete.

Image from Gyazo

On the dashboard, you can confirm that it’s deployed under the domain issued by Vercel.

Image from Gyazo

Access the domain issued by Vercel.

The top page shows the same content as in your local environment.

Image from Gyazo

Page navigation works fine and blog details are displayed as well.

Image from Gyazo

Configure Webhooks

We’ve set blog details to be pre-generated (generated at build time).
In that case, even if a new article is created, it won’t appear until a build is run.

Next.js builds are automatically triggered by Vercel when code is merged into the main branch on GitHub, so the development-to-deploy flow is smooth.

However, creating blog posts does not automatically trigger a build.

So we’ll use Webhooks to notify Vercel of blog updates from microCMS and trigger a build.

This way, updating the blog will directly lead to updated blog display.

Create an API in Vercel to trigger deployment

Under “Settings” -> “Git”, there is a “Deploy Hooks” section. Enter values as shown in the image and click “Create Hook” to generate an API URL.

Image from Gyazo

Register the Webhook in microCMS

In the “Blog” content’s “API settings” -> “Webhook”, add a Webhook.

Image from Gyazo

Register the API URL you obtained from Vercel.

You can also configure when notifications are sent; besides content publication, you can trigger deploys on actions like reordering or content unpublishing.

Image from Gyazo

That’s all for the Webhook setup.

Let’s test whether new blog posts actually appear.

Create a blog post with any content.

Image from Gyazo

A build takes about a minute, so wait a bit.

Check the top page again and you’ll see the new blog title displayed.

Image from Gyazo

Click the link and you can see the blog details as well.

Image from Gyazo

Conclusion

Through this article, I hope you’ve gotten a sense of how easily you can build a blog site with microCMS and Next.js.

Blog posts are valuable intellectual assets, so you can store them in a stable SaaS service (microCMS in this case) and customize the presentation using Next.js and CSS as you like.

By using Vercel, you can quickly publish your site and share it as your own technical blog with many people.

If your company’s existing blog site looks bad or has performance issues, I hope this can be a trigger to consider migrating.

And if you're building or planning to build your own technical blog, I hope this serves as one possible architecture for reference.

Xでシェア
Facebookでシェア
LinkedInでシェア

Questions about this article 📝

If you have any questions or feedback about the content, please feel free to contact us.
Go to inquiry form