Tidying Up the Final Product
Tags: javascript, webdev, tutorial, t3stack
Published: 2022-08-04 08:26:36

# Initial Set Up

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)**

# Main Goals in this article

Here are some of our main goals to work through in this article

1. Rewrite the existing slugs for the article pages so they map from `<url>/blog/1` which is honestly kind of ugly to `<url>/blog/<slug generated from title>`.
2. Refactor main page to use SSG instead of a tRPC useQuery hook which might result in me getting rate-limited at some point
3. Prettifying the main page

# Rewriting Existing Slugs

## Previous Configuration

In Part 2 of this series, we looked at  [using Github Issues as a CMS](https://dev.to/ivanleomk/part-2-rendering-github-issues-from-markdown-to-html-on-my-blog-5hme). One of the things that we implemented there was to grab data from Github Issues using the issue number assigned to the article.

This resulted in url slugs such as `<url>/blog/1` which admittedly was not the most satisfying to look at. I was hoping instead to be able to have article urls that looked more like `<url>/blog/<slug generated from title>`. 

> If you're unsure what a slug is, it is a modified version of a string which can be used in a url. A good example might be of an Article which is titled "Adding a Table Of Contents To Blog Articles" that can be converted into a slug `adding-a-table-of-contents-to-blog-articles`. Notice here that all letters are lowercased and spaces are replaced with `-`.

Let's first start by writing a quick function to generate slugs for our individual titles. We can do so by chaining a series of `replace` function calls

```js
export const slugify = (text: string) => {
  return text
    .toString()
    .toLowerCase()
    .replace(/\s+/g, "-") // Replace spaces with -
    .replace(/[^\w-]+/g, "") // Remove all non-word chars
    .replace(/--+/g, "-") // Replace multiple - with single -
    .replace(/^-+/, "") // Trim - from start of text
    .replace(/-+$/, ""); // Trim - from end of text
};
```

This allows us to now generate our slugs. Now we need to rewrite our `[issueId].tsx` file which will statically generate all the relevant pages for each respective article.

## Refactoring `getStaticProps`

Let's first rename `[issueId].tsx` to `[slug].tsx` so it more accurately reflects what we are trying to achieve. We have two functions to modify/write:

1. `getPostIDs` : This grabs all the individual Issue Ids from the repository and returns a list containing all the individual article titles.
2. `getSinglePost` : This grabs the information for a post given a slug that we provide

## `getPostIDs` 

Currently, we are returning the individual issue Ids of the various issues that we have in our respository using the function as seen below

```js
export const getPostIds: () => Promise<number[]> = async () => {
  const { repository } = await graphqlWithAuth(
    `query getPostIds {
      repository(owner: "ivanleomk", name: "personal_website_v2") {
        issues(last: 100) {
            nodes {
                number
            }
        }
      }
  }
`,
    {}
  );

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

What we want is the titles of the individual articles so we should just replace the `number` with the `title` instead. This gives us a new function which looks something like what we see below

```js
export const getPostIds: () => Promise<string[]> = async () => {
  const { repository } = await graphqlWithAuth(
    `query getPostIds {
      repository(owner: "ivanleomk", name: "personal_website_v2") {
        issues(last: 100) {
            nodes {
                
                title
            }
        }
      }
  }
`
  );

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

What we see in turn is that this now returns for us a list of the individual titles. We can then call this in `getStaticPaths` in order to generate the relevant paths for our specific function as seen below.

> If you're unaware of what getStaticPaths does, it basically generates a list of paths that can be matched using the `[slug]` pattern. By doing so, we can prevent users from accessing unauthorized/invalid paths and is a feature that is built into NextJS.

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

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

## `getSinglePost`

I struggled quite a bit trying to find an optimal solution to this and realised that I was indeed dealing with a Yacht problem. You can read more about it [here](https://twitter.com/lauraklein/status/1553066885746331648). As a result, I realised that I should prioritise banging out a solid prototype and instead work on optimizing it down to road. 

After giving it some thought, I realised that what might be a potential solution would be to

1. Get relevant data on every single post in the form of a humoungous query
2. Filter through all this data and find a post with a title that matches the slug
4. Return the data that has been filtered

I think the main downside with this approach is that I am making multiple repetitive calls to the same API for the same payload but I wasn't quite sure how to best memoize the data when generating data with NextjS. We first begin by writing out a quick graphql request which will give us all the data that we need as seen below.

```graphql
query getPost{
      repository(owner: "ivanleomk", name: "personal_website_v2") {
        issues(last:100){
          edges{
            node{
              title
              number
              createdAt
              body
            }
          }
        }
      }
```

We then filter through this entire list of information by using a simple `filter` higher order function

```
const post = repository.issues.edges.filter((issue) => {
    return slugify(issue.node.title) === slug;
  })[0].node;
```

before finally returning the single element node which corresponds to the valid article. We can combine this in the form of a function `getSinglePost` as seen below

```js
export const getSinglePost: (slug: string) => Promise<githubPost> = async (
  slug: string
) => {
  const { repository } = await graphqlWithAuth(
    `
    query getPost{
      repository(owner: "ivanleomk", name: "personal_website_v2") {
        issues(last:100){
          edges{
            node{
              title
              number
              createdAt
              body
            }
          }
        }
      }
}
    `
  );

  const post = repository.issues.edges.filter((issue) => {
    return slugify(issue.node.title) === slug;
  })[0].node;

  return post;
};
```

We can then call this in our `getStaticProps` function. This replaces our initial `getPostByIssueId` that we wrote in [Part 2](https://dev.to/ivanleomk/part-2-rendering-github-issues-from-markdown-to-html-on-my-blog-5hme)

Once this is done, we can then return this in our `getStaticProps` function as seen below.

```js
export async function getStaticProps({ params }: BlogPostParams) {
  const { slug } = params;
  // const post = await getPostByIssueId(parseInt(issueId));
  const post = await getSinglePost(slugify(slug));
  const { title, body, createdAt } = post;

  const { content: parsedBody } = matter(body);

  const content = await renderToHTML(parsedBody);

  return {
    props: {
      content: String(content),
      title,
      createdAt,
      rawContent: body,
    },
  };
```

The code inside is otherwise identical. We can then save our changes, refresh the page and voila, we have now ported over our articles to the new url standard. We just need to update the links that we populate in `PostLink` to utilise this new url convention and we have now ported our article pages over to the new URL standard

```js
{posts.data?.posts?.map((item) => {
        return <PostLink key={item.title} post={item} />;
 })}
```

# Refactoring `index.tsx` to use SSG instead of useQuery

## Why we need to refactor this .tsx option

Currently, our `index.tsx` looks something like this

```
import type { NextPage } from "next";
import Head from "next/head";
import PostLink from "../components/PostLink";
import { trpc } from "../utils/trpc";

const Home: NextPage = () => {
  const posts = trpc.useQuery(["github.get-posts"]);

  return (
    <>
      <h1>Posts</h1>
      {posts.data?.posts?.map((item) => {
        return <PostLink key={item.title} post={item} />;
      })}
    </>
  );
};

export default Home;
```

it does the job but the problem with this is that everytime someone loads the page, we are making a call to the github API. What this means is that if 1000 people access my site within an hour, I will be rate-limited. As a result, I think it's a pretty glaring error to fix.

This is where our wonderful friend `getStaticProps` comes in! Notice here that we don't need to declare `getStaticPaths` because we are not matching on a wildcard like we do in `[slug].tsx` . Instead we only have a single defined route, that is `<url>/` that we match in `index.tsx`.

Therefore, we can add in a `getStaticProps` function that loads up the entire list of posts when the index page is being generated. Let's quickly scaffold this out.

## Writing out a `getStaticProps` function

We already have an existing function which we have written called `getPublishedPosts` which extracts out metadata of all published posts. However, since it's the front page, I'll like to only display the latest 10 posts instead of all my posts. That way users don't feel overwhelmed by my website and can see more recent and up-to-date content. We first begin by rewriting our `getPublishedPosts` function to only return 10 articles at a time. 

```js
export const getPublishedPosts: () => Promise<githubPostTitle[]> = async () => {
  const { repository } = await graphqlWithAuth(`
        {
          repository(owner: "ivanleomk", name: "personal_website_v2") {
            issues(last: 10) {
              edges {
                node {
                    number
                    title
                    createdAt
                    body
                    labels(first: 3) {
                        nodes{
                            name
                        }
                    }
                }
              }
            }
          }
        }
      `);

  return (
    repository.issues.edges
      //@ts-ignore
      .map(({ node }) => {
        return { ...node };
      })
      .filter((post: githubPostTitle) => {
        return post.labels.nodes.some((label) => label.name === "published");
      })
  );
};
```

We can then call this in the  `getStaticProps` of our `Home` component

```js
export async function getStaticProps() {
  const posts = await getPublishedPosts();
  return {
    props: {
      posts,
    },
  };
}
```

which allows us to pass in a list of `Posts` into our component as a prop. We can then utilise our previous `githubPost` declaration which we wrote in a previous post to type this prop, thus allowing us to generate the new Home page as seen below

```
import Head from "next/head";
import PostLink from "../components/PostLink";
import { getPublishedPosts, githubPostTitle } from "../utils/github";

type HomePageProps = {
  posts: githubPostTitle[];
};

const Home = ({ posts }: HomePageProps) => {
  return (
    <>
      <h1>Posts</h1>
      {posts &&
        posts?.map((item) => {
          return <PostLink key={item.title} post={item} />;
        })}
    </>
  );
};

export async function getStaticProps() {
  const posts = await getPublishedPosts();
  return {
    props: {
      posts,
    },
  };
}

export default Home;
```

And with this, we've successfully ported over our index page to a statically generated page using SSG! Now all that is left to do is to fix up the post links that we've generated. Ideally, we want to add more metadata and information for them so that users can make more educated choices when browsing through them.

Currently they look like this

<img width="381" alt="Screenshot 2022-07-31 at 9 53 13 PM" src="https://user-images.githubusercontent.com/45760326/182029625-70cbade5-07bd-4d22-b169-35433bd41827.png">

and I think we can do a lot better. Let's now add a little bit of parsing such that we display the data which the 

- The date it was created
- The tags which I want to display for this specific article 

Feel free to copy the following code in order to achieve the desired result

![Screenshot 2022-08-04 at 1 32 18 PM](https://user-images.githubusercontent.com/45760326/182770143-c1452c77-76a5-45bb-8f09-ed7e8fd1d04d.png)

which corresponds to the code as seen below

```js
import PostLink from "../components/PostLink";
import { getPublishedPosts, githubPostTitle } from "../utils/github";

type HomePageProps = {
  posts: githubPostTitle[];
};

const Home = ({ posts }: HomePageProps) => {
  return (
    <div className="flex items-center justify-center mt-10">
      <div className="max-w-4xl w-full ">
        <div>
          <h2 className="text-3xl font-extrabold tracking-tight sm:text-4xl">
            Yo I&apos;m Ivan
          </h2>
          <p className="text-xl text-gray-500">I&apos;m a software engineer</p>
        </div>
        <div className="container mx-auto mt-10">
          <div className="max-w-lg">
            <div className="flex flex-col items-start justify-content">
              <h1 className="font-bold text-lg tracking-tight">Latest Posts</h1>
              <ul>
                {posts &&
                  posts?.map((item) => {
                    return <PostLink key={item.title} post={item} />;
                  })}
              </ul>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export async function getStaticProps() {
  const posts = await getPublishedPosts();
  return {
    props: {
      posts,
    },
  };
}

export default Home;
```

And the code for the post link. Note that you'll need to install `day.js` in order to be able to nicely format the code.

```js
import Link from "next/link";
import React from "react";
import { githubPostTitle } from "../utils/github";
import { slugify } from "../utils/string";
import dayjs from "dayjs";

type PostLinkProps = {
  post: githubPostTitle;
};

const PostLink = ({ post }: PostLinkProps) => {
  return (
    <Link href={`blog/${slugify(post.title)}`}>
      <div className=" py-3 pl-5">
        <p className="cursor-pointer hover:underline">
          {post.title} | {dayjs(post.createdAt).format("DD-MM-YYYY")}
        </p>
        <div className="ml-2 flex-shrink-0 flex"></div>
      </div>
    </Link>
  );
};

export default PostLink;
```

Now all that's left is to automatically generate code which will allow us to display each individual series of posts which we've written! We'll cover this in the next article so stay tuned! :)