Using Github Issue as CMS

I recently started to use github issue as my CMS. Previously I was trying to use NetlifyCMS as my CMS. But then I found out that Github Issue is more simple & easier. So I decided to experiment with this. So far so good.

And yes, this blog is power Github Issue + NextJS + Netlify.

This blog is open source, so you can actually see how it works here. https://github.com/muhajirdev/muhajir.dev/

So how does it works ?

Fetching the data

Github has a solid GraphQL api that we can use to fetch the data. Here's how I fetch it.

https://github.com/muhajirdev/muhajir.dev/blob/77e221d158e9a25d9c71a2d67b927f38f426f058/lib/github-graphql-client.js#L1-L46

You can change variable name and owner to your repository name and github username.

const { graphql } = require('@octokit/graphql')

const TOKEN = process.env.TOKEN

const request = graphql.defaults({
  headers: {
    authorization: `token ${TOKEN}`,
  },
})

export const getPosts = () =>
  request(
    `{
    repository(name: "muhajir.dev", owner: "muhajirdev") {
      issues(first: 50) {
        nodes {
          title
          number
          createdAt
          bodyHTML
        }
      }
    }
  }
`
  ).then((data) => data.repository.issues.nodes)

export const getPost = (number) => {
  return request(
    `query getPost($number: Int!){
      repository(name: "muhajir.dev", owner: "muhajirdev") {
        issue(number: $number) {
            title
            number
            createdAt
            bodyHTML
        }
      }
  }
`,
    {
      number: Number(number),
    }
  ).then((data) => data.repository.issue)
}

Creating github token.

We can create github issue in Settings -> Personal Access Token -> Generate New Token

image

Rendering it on NextJS

You can see how I render it on here https://github.com/muhajirdev/muhajir.dev/blob/master/pages/index.js

import { getPosts } from 'lib/github-graphql-client'

...

const index = ({ title, description, posts, ...props }) => {
  return (
    <Layout pageTitle={title}>
      <main>
        <Hero />
        <div className="mt-24">
          <h1 className="text-3xl font-bold mb-8">Writings</h1>
          <PostList posts={posts} />
        </div>
      </main>
    </Layout>
  )
}

export default index

export async function getStaticProps() {
  const configData = await import(`../siteconfig.json`)
  const posts = await getPosts()

  return {
    props: {
      posts,
      title: configData.default.title,
      description: configData.default.description,
    },
  }
}

https://github.com/muhajirdev/muhajir.dev/blob/master/components/postlist.js

import Link from 'next/link'
import slug from 'slug'

export default function PostList({ posts }) {
  return (
    <div>
      {posts.length <= 0 && <div>No posts!</div>}
      <div className="flex -mx-8">
        {posts &&
          posts.map((post) => {
            const postSlug = slug(post.title)
            return (
              <Link
                href={'/post/[...post]'}
                as={`/post/${post.number}/${postSlug}`}
                key={postSlug}
              >
                <a className="mb-4 shadow-md rounded-md h-64 p-6 w-64 mx-8 flex flex-col justify-between">
                  <h2 className="text-xl tracking-wide font-bold">
                    {post.title}
                  </h2>
                  <div>
                    <p className="pb-6 text-gray-700">
                      Lorem ipsum dolor sit amet consectetur adipisicing...
                    </p>
                    <div className="border-b-4 w-24" />
                  </div>
                </a>
              </Link>
            )
          })}
      </div>
    </div>
  )
}

https://github.com/muhajirdev/muhajir.dev/blob/master/pages/post/%5B...post%5D.js

import Layout from '@components/layout'
import { getPost, getPosts } from 'lib/github-graphql-client'
import slug from 'slug'

export default function BlogPost({ title, bodyHTML }) {
  return (
    <Layout pageTitle={title}>
      <article className="max-w-4xl post">
        <h1 className="text-5xl font-bold mb-4">{title}</h1>
        <div
          dangerouslySetInnerHTML={{
            __html: bodyHTML,
          }}
        ></div>
      </article>
    </Layout>
  )
}

export async function getStaticProps({ ...ctx }) {
  const { post: postParam } = ctx.params
  const [id, slug] = postParam

  const post = await getPost(id)

  return {
    props: {
      title: post.title,
      bodyHTML: post.bodyHTML,
    },
  }
}

export async function getStaticPaths() {
  const posts = await getPosts()
  const paths = posts.map((post) => `/post/${post.number}/${slug(post.title)}`)

  return {
    paths,
    fallback: false,
  }
}

Let's try it

TOKEN=yourgithubtoken yarn build
yarn export

Now you should be able to see generated folder out.

Let's try to serve it

npx serve -d out

Deploy it to Netlify

Just register a netlify account. And then, connect this repo to netlify

Rebuild the site when there's a new issue with Webhook

Now go to netlify Settings -> Build & Deploy section. Click Add build hook and then copy the url.

Go to your github repository Settings -> Webhook. Click Add Webhook

image

Choose Let me select invidiual events and select Issues

image

Now your site should be updated everytime you create new issue on your repo

Reach me on twitter @muhajirdev
This site is built with NextJS, hosted on Netlify, and uses Github Issues as CMS