The engineering behind octotype: GitHub Issues as CMS, Next.js, and 8 times OpenAI saved me

Originally published as a GitHub Issue on ยท 6 minutes read

This is the story of how I made this site, octotype.

First of all, being a lousy programmer as I am, I wanted to minimize damage and use a stack that maximizes the developer experience. For me, a good developer experience creates spaces for you to focus on fixing the business problem at hand. It's not about magic tools that do hundred of things. That's why the following stack fits the bill for me:

  • GitHub Issues as CMS ๐Ÿ“š. GitHub's Issue management system gives me all I need to create and manage content behind the scenes. I am very familiar with it, and it's easy to use, widely adopted, and has an out-of-the-box API with a good free tier. It's just perfect!
  • Next.js with the Blog template โœ๐Ÿป. My new blog site is leaning heavily on this template, and I only added a few new types, components, and UI functionality to fetch data from GitHub through its API, such as issues (as posts), comments, and reactions. Of course, it's a statically generated site, but I have used Incremental Site Regeneration, so new issues created in GitHub Issues show up as blog posts after a very short while. It's just perfect and lightning-fast. And ISR even works with dynamic paths!
  • Typescript ๐Ÿ’ป. I feel very comfortable programming with types. I think it forces you into a kind of healthy DDD thinking and way of programming.
  • Vercel as frontend platform ๐Ÿš€. Nothing to add, you know I am in love with Vercel
  • OpenAI ๐Ÿค–. Down below, I share 8 times ChatGPT saved my ass. I am still in absolute awe.

Wait, OpenAI? For what? Well, building the whole new site took me about two (2) days. Seriously. It may not sound like a lot to you, but it's a remarkable achievement for someone like me who does not code daily. I have used it extensively to customize the app created from the Next.js template, and ChatGPT helped me to write Tailwind, Typescript, and other Next.js code when I hit a wall. This was one of the main reasons I wanted to build my own site: I felt capable of doing something like this in a reasonable amount of time with this tool. Based on my comments about the developer experience above, I can say that ChatGPT provided me with an Augmented Developer Experience.

The frontend work

The octotype site is 100% pre-rendered, which gives us an optimal user experience with a super fast site. This means that the landing page, every user page, and every blog post are pre-rendered on the server using Next.js ISR with an interval of 120 seconds. The data in these pages are generated by fetching the content directly from the GitHub API.

Using Next.js, I managed to maintain simple yet dynamic URL routes like /:username/stories/:number. Next.js provides a beautiful out-of-the-box feature to help implement this use case where you need to handle combinatorial paths, and it's called optional catch-all routes. Using this feature, I could have added a [[...slug]].tsx file to rule them all, and that would have helped me catch routes such as:

- /
- /peibolsang
- /peibolsang/stories
- /peibolsang/stories/1

However, since optional catch-all routes are implemented in one TSX file, this would have forced me to implement different layouts on one page with a lot of conditional logic to render different large UI components for each layout. What did I do instead? I didn't know it until I googled it, but you can actually apply catch-all routes to folder names, too, as long as you include an index.tsx file under that folder. In other words, [username].tsx is the same as [username]/index.tsx ๐Ÿคท๐Ÿปโ€โ™‚๏ธ. This allowed me to create the following folder structure where I can pre-render every page using getStaticProps and getStaticPaths:

- pages/index.tsx
- pages/[username]/index.tsx
- pages/[username]/stories/[slug].tsx

This also gives me some future proofing, just in case I want to switch from pre-rendering using getStaticProps to SSR using getServerSideProps for some of the pages. Why would I want to do that? Keep reading ๐Ÿ˜‚

Using GitHub's API

This MVP of octotype uses the GitHub API free tier, which has a rate limit of 5000 calls per hour. Why did I choose this? Let me put some use cases based on how the octotype site is designed (i.e., how the app calls the GitHub API in the getStaticProps and getStaticPaths functions):

  • Scenario 1: 10 octotype users with 5 posts each with a site regeneration every 120 seconds will fire about 3900 API calls. This is still reasonable and within the API rate limits, and we can pre-render all blog posts in this scenario!
  • Scenario 2: An increment to 15 octotype users with 5 posts each with a site regeneration every 120 seconds will trigger about 5940 API calls, which is above the rate limit of 5000.

As you can see, the site's scalability is limited due to the API rate restriction, so how can we fix it? I wish I had this problem (๐Ÿ˜‡), but I see different options:

  1. We could increment the ISR revalidation from 120 seconds to 180 seconds or beyond. This will trigger API calls less frequently, of course. That's cool and all that, but users would see a delay of 3 mins between the issue being created on GitHub and the blog post being published on the Web.

  2. For immediate rendering without delay, we could server-side render blog posts which, while detrimental to performance, is a safe option because it will reduce the number of API calls in an order of magnitude.

  3. I am unsure whether creating a GitHub App or an OAuth App is a valid option since I'd prefer to stick to the GitHub API free tier. Why? I sought simplicity and wanted to avoid the user logging in with GitHub credentials to get a better API rate limit. For me forcing the user to log in with GitHub credentials just to read content was a deal breaker. The audience of every developer's blog shouldn't be restricted to other developers only!

As you can imagine, I hit the limit multiple times while building the site using example Issues. However, Next.js gives us a super handy way of adding local environment variables out of the box. To get a better API rate during development time, I just had to put my personal GitHub token in the .env.local file and add a feature flag to use it if it was available (which is not the case on the server)

Finally, rendering the markdown of a GitHub issue as HTML was super easy too. The API itself returns a raw markdown, so you only need a small library like remark to parse it and transform it into HTML. Something like this:

import { remark } from 'remark'
import html from 'remark-html'

export default async function markdownToHtml(markdown: string) {
  const result = await remark().use(html).process(markdown)
  return result.toString()

8 Examples of Augmented Developer Experience with OpenAI

As I mentioned earlier, I wouldn't have built this site without ChatGPT. Here are some very good examples as screenshots of the type of inputs I gave to the tool together with the provided answer. Did it work? Well, if you are reading this post through octotype's Web, yes, it is. Beautifully. BIG TIME

(Note: dont' even judge me ๐Ÿ˜‡)

Example 1: Problem with async/await: I just copy-pasted a code snippet. Then the AI detected the problem and came up with a solution.

Screenshot 2023-01-08 at 20 35 38

Example 2: I wanted to implement a simple tabs component: There you go, say no more

Screenshot 2023-01-08 at 20 38 08

Example 3: I did ask the same for a carousel component: It didn't work at first because of a bug. The AI fixed it:

Screenshot 2023-01-08 at 20 39 20

Example 4: An easy one about pure UI aspects using TailwindCSS.

Screenshot 2023-01-08 at 20 40 40

Example 5: A basic Typescript question: Because of stupid me ๐Ÿ˜‚

Screenshot 2023-01-08 at 20 42 05

Example 6: Using Next.js getStaticProps: These are my programming skills!

Screenshot 2023-01-08 at 20 43 24

Example 7: The typewriter effect on the landing page: This is how I did it

Screenshot 2023-01-08 at 20 44 44

Example 8: More on the typewriter effect.

Screenshot 2023-01-08 at 20 44 44

Comments (1)


Bonus: This one also blew my mind

Screenshot 2023-01-08 at 21 40 29