Part 2 : Using Github Issues as a CMS
Tags: javascript, webdev, t3stack, tutorial
Published: 2022-07-28 08:22:00

## Quick Introduction

This is a series of blog posts which detail how I'm setting up my website with the t3 stack. If you haven't read the previous articles, I suggest looking at 

- Part 1. : [Redesigning My Website with the t3-stack](https://www.ivanleo.com/blog/setting-up-the-new-blog)
- Part 2 : **[Displaying Individual Articles](https://www.ivanleo.com/blog/displaying-individual-articles)**
- Part 3 : [Adding a table of contents to our blog articles](https://www.ivanleo.com/blog/adding-a-table-of-contents-to-blog-articles)
- Part 4 : [Tidying Up the final product](https://www.ivanleo.com/blog/tidying-up-the-article-page-and-adding-ssg-to-the-home-page)

## Current State

Previously, we successfully managed to get our website to render out the list of issues we had configured on Github using the t3-stack. If you haven't read it yet, you can read it [here](https://dev.to/ivanleomk/redesigning-my-website-with-the-t3-stack-3l8a)

 However, now we want to do something a bit different, we want to be able to render these articles and dynamically generate pages that correspond to each of these articles.

For reference here's how the main page looks like now

![image](https://user-images.githubusercontent.com/45760326/181430590-eff1da91-e75f-42d4-9a22-4dd5f1c837c0.png)

It's not super inspiring but we've got it working such that we only list articles that are labelled as `published`. **This is a big step for us**. 

Today in this article, we have two main goals

- Using Static Generation to create individual pages for each article
- Rendering out the content we've written in markdown as HTML in the page


## Creating individual pages

### Installing New Libraries

In this walkthrough, we will be using the following libraries 

- `unified`
- `gray-matter`
- `remark-parse`
- `remark-rehype`
- `rehype-stringify`

So let's go ahead and install the libraries with `npm` as seen below.

```
npm i - s unified gray-matter remark-parse remark-rehype rehype-stringify
```

### Walkthrough

In NextJS, we are able to create dynamic routes, which are essentially routes that are generated.  Let's start by creating a file `src/pages/blog/[issueId].tsx`. By using the `[...]` syntax, we are able to indicate to NextJS that we want to create a dynamic route.

In this case, what this means is that the following routes

- <url>/blog/2
- <url>/blog/thisisarandomstring

will all match and be rendered by the [issueId] file. 

> How do we then know what are valid routes and invalid routes if any random combination that matches the specification is able to be rendered?

The answer to that is  a function called `getStaticPaths`. This tells NextJS what are the paths that we wish to support and in turn it will do the matching on the backend for us. If we attempt to access an invalid path, it will simply return a `404 Page`. Let's first begin by writing out a quick function which allows us to get a list of all the individual Issue Ids.

```js
export const getPostIds: () => Promise<number[]> = async () => {
  const { repository } = await graphqlWithAuth(
    `query getPostIds {
      repository(owner: <Your Github Username> , name: <Your Repository name>) {
        issues(last: 200) {
            nodes {
                number
            }
        }
      }
  }
`,
    {}
  );

  // Quick Type Definition here
  return repository.issues.nodes.map(
    (issue: { number: string }) => parseInt(issue.number)
  );
};
```

Let's breakdown what is happening in this specific function

```graphql
 repository(owner: <Your Github Username> , name: <Your Repository name>) {
        issues(last: 200) {
            nodes {
                number
            }
        }
      }
  }
```

> Recall that Github Issues can be identified by a unique number. **This is a property which exists on the Issue Object in the Github API** .

We extract out the list of all existing github issue numbers and transform it into an array. Since I currently have 2 published articles, this gives 

```js
[1,2]
```

We specify that we want the issues from <Your Repository Name> which is owned by <Your Github Username> and that allows us to get the correct issues we are looking for.

Now that we have a list of post Ids, we can now generate our list of paths using `getStaticPaths` as seen below

```js
export async function getStaticPaths() {
  const posts = await getPostIds();
  const paths = posts.map((issueId) => `/blog/${issueId}`);

  return {
    paths,
    fallback: false,
  };
}
```

Now that we have indicated a list of paths, we need to now tell NextJS how to render each specific page, that's where `getStaticProps` comes in handy. 

> Note : I previously made the mistake of writing getStaticProps using my NextJS APIs. This caused the production build to fail **multiple times**. Please don't make the mistake I did and just write a wrapper function around the code that handles the server communication. It will save you years of pain.

But before we use `getStaticProps`, we need to first write a small function that will allow us to obtain the metadata and raw markdown for our code given an Issue Id. This can be done by making another API call to the GraphQL API as seen below

```js
export const getPostByIssueId: (
  issueId: number
) => Promise<githubPost> = async (issueId) => {
  const { repository } = await graphqlWithAuth(
    `query getPost($number: Int!){
      repository(owner: <Your Github Username>, name: <Your Github Repository>) {
        issue(number: $number) {
            title
            number
            createdAt
            body
        }
      }
  }
`,
    {
      number: Number(issueId),
    }
  );

  return repository.issue;
};
```

with the corresponding type definition of `githubPost` seen below

```ts
export type githubPost = {
  title: string;
  number: string;
  createdAt: string;
  body: string;
};
```

We can then utilise this in our main component as seen below

```js
export async function getStaticProps({ params }: BlogPostParams) {
  const { issueId } = params;
  const post = await getPostByIssueId(parseInt(issueId));
  const { title, body } = post;
  const { content: parsedBody } = matter(body);

  const content = await unified()
    .use(remarkParse)
    .use(remarkRehype, { allowDangerousHtml: true })
    .use(rehypeStringify, { allowDangerousHtml: true })
    .process(parsedBody);

  return {
    props: {
      content: String(content),
    },
  };
}

export async function getStaticPaths() {
  const posts = await getPostIds();
  const paths = posts.map((issueId) => `/blog/${issueId}`);

  return {
    paths,
    fallback: false,
  };
}
```

Where the following chunk basically takes a raw markdown text and rehydrates it in order to generate HTML

```js
  const { title, body } = post;
  const { content: parsedBody } = matter(body);

  const content = await unified()
    .use(remarkParse)
    .use(remarkRehype, { allowDangerousHtml: true })
    .use(rehypeStringify, { allowDangerousHtml: true })
    .process(parsedBody);

```

We can now put together a simple initial component which gives us the following output

```js
import { getPostByIssueId, getPostIds } from "../../utils/github";
import matter from "gray-matter";

import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import Link from "next/link";

type BlogPostProps = {
  title: string;
  content: string;
};

type BlogPostParams = {
  params: { issueId: string };
};

export default function BlogPost({ title, content }: BlogPostProps) {
  return (
    <>
      <h1>{title}</h1>
      <div
        dangerouslySetInnerHTML={{
          __html: content,
        }}
      />
    </>
  );
}

export async function getStaticProps({ params }: BlogPostParams) {
  const { issueId } = params;
  const post = await getPostByIssueId(parseInt(issueId));
  const { title, body } = post;
  const { content: parsedBody } = matter(body);

  const content = await unified()
    .use(remarkParse)
    .use(remarkRehype, { allowDangerousHtml: true })
    .use(rehypeStringify, { allowDangerousHtml: true })
    .process(parsedBody);

  return {
    props: {
      content: String(content),
    },
  };
}

export async function getStaticPaths() {
  const posts = await getPostIds();
  const paths = posts.map((issueId) => `/blog/${issueId}`);

  return {
    paths,
    fallback: false,
  };
}

```

Which generates the following output
<img width="1286" alt="Screenshot 2022-07-28 at 4 12 10 PM" src="https://user-images.githubusercontent.com/45760326/181455888-8cd9ec8b-6e7b-4c1e-9409-740b81865195.png">

So now that we've got the content, let's work on styling the specific page now.

## Styling the page

For this specific portion, we'll be using Tailwind to style our article section. If you didn't install TailwindCSS, please follow the instructions [here](https://tailwindcss.com/docs/guides/nextjs) and get it set up first.

> Tailwind provides a useful utility class called prose which applies automatic styles for us into the rendered HTML so that we don't need to generate custom rules.

But before we can plug it in, we need to install a new plugin called `@tailwindcss/typography`. You can read about it [here](https://tailwindcss.com/docs/typography-plugin). Execute the command below to install the plugin

```
npm install -D @tailwindcss/typography
```

and then modify your `tailwind.config.js` file accordingly as seen below

```js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [require("@tailwindcss/typography")],
};
```

Notice here that we've added in `@tailwindcss/typography` in the plugins section. We can then rewrite our existing component as 

```ts
export default function BlogPost({ title, content }: BlogPostProps) {
  return (
    <>
      <Link
        href={{
          pathname: "/",
        }}
      >
        <p> ← Go Back Home</p>
      </Link>
   // We Added This!
      <div className="prose mx-auto">
        <h1>{title}</h1>
        <div
          dangerouslySetInnerHTML={{
            __html: content,
          }}
        />
      </div>
    </>
  );
}
```

which gives the following output as 

<img width="545" alt="Screenshot 2022-07-28 at 4 16 30 PM" src="https://user-images.githubusercontent.com/45760326/181456688-51ec376c-d126-4f6b-aedc-a1de6b553840.png">

Voila! Now we have basic styling for any article that we write in our github issues as long as we tag it with the `published` label. In the next article, we'll look at adding a table of contents to our article which is automatically generated based on our content.