md0 CMS works seamlessly with Gatsby. Content is managed visually in md0 CMS and queried through Gatsby's GraphQL layer—no configuration changes needed.
How It Works
The Architecture
GitHub Repository (md0 CMS edits here)
├── content/
│ └── posts/
│ └── *.md
└── src/
└── pages/
└── blog/ ← Gatsby queries via GraphQL
Workflow:
- Edit content in md0 CMS
- Changes commit to GitHub
- Gatsby rebuilds and queries markdown
- GraphQL layer provides data
Setup
Install Dependencies
npm install gatsby-source-filesystem gatsby-transformer-remark
Configure Gatsby
gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/content/posts`,
},
},
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 1200,
},
},
`gatsby-remark-prismjs`,
],
},
},
],
};
Blog Example
List All Posts
src/pages/blog.js:
import React from "react";
import { graphql, Link } from "gatsby";
export const query = graphql`
query BlogPosts {
allMarkdownRemark(
sort: { frontmatter: { date: DESC } }
filter: { frontmatter: { published: { eq: true } } }
) {
nodes {
id
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
excerpt
tags
}
fields {
slug
}
}
}
}
`;
export default function BlogPage({ data }) {
const posts = data.allMarkdownRemark.nodes;
return (
<div>
<h1>Blog</h1>
{posts.map((post) => (
<article key={post.id}>
<Link to={`/blog/${post.fields.slug}`}>
<h2>{post.frontmatter.title}</h2>
</Link>
<time>{post.frontmatter.date}</time>
<p>{post.frontmatter.excerpt}</p>
<div>
{post.frontmatter.tags?.map((tag) => (
<span key={tag}>{tag}</span>
))}
</div>
</article>
))}
</div>
);
}
Create Post Pages
gatsby-node.js:
const path = require("path");
const { createFilePath } = require("gatsby-source-filesystem");
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type === "MarkdownRemark") {
const slug = createFilePath({ node, getNode, basePath: "posts" });
createNodeField({
node,
name: "slug",
value: slug,
});
}
};
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
query {
allMarkdownRemark {
nodes {
fields {
slug
}
}
}
}
`);
result.data.allMarkdownRemark.nodes.forEach((node) => {
createPage({
path: `/blog${node.fields.slug}`,
component: path.resolve("./src/templates/blog-post.js"),
context: {
slug: node.fields.slug,
},
});
});
};
Post Template
src/templates/blog-post.js:
import React from "react";
import { graphql } from "gatsby";
export const query = graphql`
query ($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
author
tags
featured_image
}
}
}
`;
export default function BlogPost({ data }) {
const post = data.markdownRemark;
return (
<article>
<h1>{post.frontmatter.title}</h1>
<time>{post.frontmatter.date}</time>
<div>By {post.frontmatter.author}</div>
{post.frontmatter.featured_image && (
<img
src={post.frontmatter.featured_image}
alt={post.frontmatter.title}
/>
)}
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
);
}
With MDX
Install MDX Plugin
npm install gatsby-plugin-mdx @mdx-js/react
Configure
gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/content/posts`,
},
},
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.md`, `.mdx`],
gatsbyRemarkPlugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 1200,
},
},
],
},
},
],
};
Query MDX
export const query = graphql`
query {
allMdx(sort: { frontmatter: { date: DESC } }) {
nodes {
id
frontmatter {
title
date
}
fields {
slug
}
}
}
}
`;
Advanced Queries
Filter by Tag
export const query = graphql`
query ($tag: String!) {
allMarkdownRemark(
filter: { frontmatter: { tags: { in: [$tag] } } }
sort: { frontmatter: { date: DESC } }
) {
nodes {
frontmatter {
title
date
}
fields {
slug
}
}
}
}
`;
Filter by Category
export const query = graphql`
query ($category: String!) {
allMarkdownRemark(
filter: { frontmatter: { category: { eq: $category } } }
) {
nodes {
frontmatter {
title
}
}
}
}
`;
Pagination
gatsby-node.js:
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const postsPerPage = 10;
const result = await graphql(`
query {
allMarkdownRemark {
totalCount
}
}
`);
const numPages = Math.ceil(
result.data.allMarkdownRemark.totalCount / postsPerPage
);
Array.from({ length: numPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/blog` : `/blog/${i + 1}`,
component: path.resolve("./src/templates/blog-list.js"),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
numPages,
currentPage: i + 1,
},
});
});
};
Image Optimization
Gatsby Image
npm install gatsby-plugin-image gatsby-plugin-sharp gatsby-transformer-sharp
Query images:
export const query = graphql`
query ($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
frontmatter {
title
featuredImage {
childImageSharp {
gatsbyImageData(width: 1200)
}
}
}
}
}
`;
Render image:
import { GatsbyImage, getImage } from "gatsby-plugin-image";
export default function BlogPost({ data }) {
const image = getImage(data.markdownRemark.frontmatter.featuredImage);
return (
<article>
{image && (
<GatsbyImage
image={image}
alt={data.markdownRemark.frontmatter.title}
/>
)}
</article>
);
}
Deployment
Gatsby Cloud
- Connect GitHub repository
- Configure build settings
- Deploy
md0 CMS commits trigger automatic rebuilds.
Netlify
- Connect repository
- Build command:
gatsby build - Publish directory:
public
Vercel
Similar to Netlify—automatic deploys on commit.
Best Practices
Frontmatter Schema
Define consistent fields:
---
title: "Post Title"
date: 2024-01-15
excerpt: "Brief summary"
author: "Author Name"
tags: [gatsby, cms]
category: "Tutorial"
featured_image: "/images/hero.jpg"
published: true
---
File Organization
content/
├── posts/
│ ├── 2024/
│ │ └── my-post.md
│ └── 2023/
└── pages/
└── about.md
GraphQL Fragments
Reuse query fragments:
export const postFields = graphql`
fragment PostFields on MarkdownRemark {
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
excerpt
author
}
fields {
slug
}
}
`;
Troubleshooting
Content Not Appearing
Check:
- File path matches
gatsby-source-filesystemconfig - Frontmatter is valid YAML
- File extension is
.mdor.mdx - Gatsby development server restarted
GraphQL Errors
Check:
- Field names match frontmatter
- Nested fields use correct syntax
- Filters use correct operators
- Query is valid GraphQL
Build Failures
Check:
- All markdown files have required frontmatter
- Image paths are correct
- No invalid characters in frontmatter
- Dependencies are installed
Next Steps
- Create Collections for Gatsby content
- Define Schemas matching frontmatter
- Start Editing in md0 CMS