Adding GitHub as a Content Source in a Next.js Blog

@bge

  • #nextjs
  • #mdx
  • #github



If you're managing blog content in a Next.js site using local MDX files and a headless CMS like GraphCMS, you can take things a step further by sourcing posts directly from a private GitHub repo.

In this post, I’ll show you how to fetch .mdx files from a private GitHub repository using the GitHub API and merge them with your existing content sources.

🧠 Why Add GitHub?

  • Store posts in GitHub for remote editing or collaboration
  • Use GitHub as a "lightweight CMS"
  • Centralize content even if your site is deployed elsewhere (like Vercel)

🔐 Step 1: Create a GitHub Token

To access private repo content, you’ll need a Personal Access Token:

  1. Go to GitHub > Settings > Developer Settings > Tokens
  2. Click “Generate new token (classic)”
  3. Check the repo scope
  4. Copy the token and store it in your .env.local:
1GITHUB_TOKEN=ghp_YourTokenHere
2GITHUB_REPO=yourusername/your-repo
3GITHUB_BRANCH=main
4

🔗 Step 2: Fetch GitHub Content in Next.js

Install dependencies:

1npm install axios gray-matter
2

Then, create a helper like lib/githubPosts.js:

1import axios from 'axios'
2import matter from 'gray-matter'
3
4const headers = {
5  Authorization: `token ${process.env.GITHUB_TOKEN}`,
6  Accept: 'application/vnd.github.v3.raw',
7}
8
9export async function getGitHubPostsList() {
10  const res = await axios.get(
11    `https://api.github.com/repos/${process.env.GITHUB_REPO}/contents/posts?ref=${process.env.GITHUB_BRANCH}`,
12    { headers }
13  )
14
15  return res.data.filter(f => f.name.endsWith('.mdx'))
16}
17
18export async function getGitHubPostContent(filename) {
19  const rawUrl = `https://raw.githubusercontent.com/${process.env.GITHUB_REPO}/${process.env.GITHUB_BRANCH}/posts/${filename}`
20  const res = await axios.get(rawUrl, { headers })
21  return res.data
22}
23

🧩 Step 3: Merge GitHub Posts with Local and GraphCMS

In your post loader (e.g. getSortedPostsData()), combine all sources:

1const githubFiles = await getGitHubPostsList()
2const githubPosts = await Promise.all(
3  githubFiles.map(async (file) => {
4    const content = await getGitHubPostContent(file.name)
5    const { data } = matter(content)
6
7    return {
8      id: file.name.replace('.mdx', ''),
9      source: 'github',
10      ...data,
11    }
12  })
13)
14

Then merge:

1return [...localPosts, ...graphcmsPosts, ...githubPosts].sort(...)
2

📄 Step 4: Render the Post

For each post, determine the source and fetch accordingly:

1switch (source) {
2  case 'local':
3    return getLocalPostData(id)
4  case 'graphcms':
5    return getGraphCmsPostData(id)
6  case 'github':
7    return getGitHubPostData(id)
8}
9

✅ That’s It!

Now your blog can source content from:

  • Local MDX files (e.g. /posts)
  • GraphCMS (via GraphQL)
  • A private GitHub repo

It’s flexible, scalable, and keeps your content portable.


Got questions or want a starter template? Hit me up!



Back
© 2025 bowen.ge All Rights Reserved.