Setting up a NextJS Markdown Blog with Typescript
Tags: markdown, nextjs, react, typescript
Published: 2021-05-30 12:09:55

---
title: Setting up a NextJS Markdown Blog with Typescript
published: true
date: 2021-05-30 00:00:00 UTC
description: With syntax highlighting and the ability to easily add more blogs
tags: markdown, nextjs, react, typescript
canonical_url: https://bionicjulia.com/blog/setting-up-nextjs-markdown-blog-with-typescript
---

[NextJS](https://nextjs.org/) has been all the rage, especially in React-land which is where I tend to reside. There's no better way of learning (in my humble opinion), than actually trying to build something from scratch, so what better than experimenting on my personal website. 😆 It was well due for an upgrade anyway, as I was using Ruby with Middleman as my static site generator. And whilst Middleman does the job, it isn't really maintained much anymore, and functionality was starting to get stale.

So first things first, here are my basic requirements:

- Capable of hosting multiple blogs.
- Able to parse blog posts written in markdown.
- Proper syntax highlighting of code snippets in the UI.
- Quick build process with blazing speeds on the front end.
- Capable of being hosted for free e.g. on Netlify or Vercel, with one click deploy to production through Github.
- Easily extensible in the future if I want to venture beyond static blog pages.
- Allows me to use Typescript.

NextJS hosted on Vercel (the optimal solution, as Vercel created NextJS) handles all of the above easily, though the second and thirds points on markdown blog posts needs a bit more configuration. Other parts of the set up I stumbled over were the NextJS specific concepts of `getStaticProps` and `getStaticPaths`.

I therefore thought I'd write a blog post about how I got things set up because I found the process rather confusing at first. Now that everything is in place and running smoothly, it all makes sense, but it sure didn't at the time. 😅

A quick note before we start - I tend to keep my **Typescript** types in a separate file. I'll show you what this looks like right at the bottom of the article, in the Appendix, for your reference.

### Step 1: Set up NextJS.

- Create the app: `yarn create next-app --typescript` and enter the name of your app (read the setup guide [here](https://nextjs.org/docs/getting-started)).
- Check that all is running with `yarn dev` on `localhost:3000`.
- Configure the `tsconfig.json` file as per your preference.

### Step 2: Set up the skeleton page structure of your site.

- The `pages` directory is where the routes for your site are automatically determined by NextJS.
- Create new `.tsx` files for the different pages you want in your site. In our case, let's just have the one called `blog.tsx`. This means it will be accessible at `yourdomain.com/blog`.
- Clear out the boilerplate content in the `index.tsx` page and add a link to `blog` using Next's **Link** component.

```jsx
<Link href="/blog">
  <a>Blog</a>
</Link>
```

- Let's also delete the `api` folder as we won't be calling an external API to grab our blog data (all the markdown files that make up our blog posts will be stored in our repo).
- NextJS's routing also supports nested routes. So in the case of our blog, if we want to have a single post accessible at say `yourdomain.com/blog/post1`, we'll need to create a folder called `blog`. Within this folder, create a template for what a single blog post will look like by creating a new `.tsx` file, with its name in square brackets e.g. `[slug].tsx`. The square brackets tells NextJS that this is a variable file name.

### Step 3: Create your first markdown file for your blog.

You don't necessarily need to do this at this point, but I think it's helpful for illustrating how blog content flows from your head, to the markdown file, to then be parsed by NextJS and shown to the reader.

- Create a new folder called `data` (that sits at the same level as your `pages` folder), and within that, another folder called `blog` (or whatever other name you prefer).
- Create a file with a `.md` extension, within this new folder e.g. `post1.md`.
- Start the file by entering the frontmatter you want to define, then continue writing your content in markdown below that. The format needs to look something like this:

```markdown
---
title: How To Run AB Tests In React
description: 4 options I came across
publishedDate: 2021/02/14
tags:
  - react
  - ab testing
---

Start writing markdown here...
```

- Note that you can define whatever frontmatter you want - you don't need to follow what I have above. Tags will be parsed later as an array of strings that looks like `['react', 'ab testing']`.

### Step 4: Set up the functionality needed to grab all your posts from your `data` folder, along with their relevant frontmatter.

So it was around this point when I started getting confused around all the options for how we enable NextJS to pull blog post data from markdown files. There are a host of different packages you can use to achieve the same effect, so this is just one of many combos that worked for me.

- [react-markdown](https://github.com/remarkjs/react-markdown) - A markdown component for React that uses [remark](https://github.com/remarkjs/remark).
- [gray-matter](https://github.com/jonschlinkert/gray-matter) - YAML frontmatter parser.
- [react-syntax-highlighter](https://github.com/react-syntax-highlighter/react-syntax-highlighter) (using the Prism subpackage) - A syntax highlighter for React.

There was quite a lot of trial and error to get to this point, but I'm happy with how it's all set up now. So here we go:

- `yarn add react-markdown gray-matter react-syntax-highlighter` to install the packages.
- Create a new folder called `lib` and add a `utils.ts` file to store some reusable functions for getting posts and their frontmatter. Note that you don't need to do this, but as I was going to have multiple blogs in my app, this helped to keep my code DRY. Here's what my utility functions look like. You'll see what each of these functions are used for in a later step.
- Note that the argument `dataType` refers to `blog` in our case. Where I have multiple blogs on my site, I can just pass in the name of my `data` subfolder which is what makes these functions reusable e.g. on my site, I also have a separate data folder called `books` to store my [book summaries](https://bionicjulia.com/books).

```tsx
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'

const root = process.cwd()

export async function getFiles(dataType: string) {
  return fs.readdirSync(path.join(root, 'data', dataType), 'utf-8')
}

export async function getPostBySlug(dataType: string, slug: string) {
  const source = fs.readFileSync(path.join(root, 'data', dataType, `${slug}.md`), 'utf8')

  const { data, content } = matter(source)

  return {
    frontMatter: data,
    markdownBody: content,
  }
}

export async function getAllPostsWithFrontMatter(dataType: string) {
  const files = fs.readdirSync(path.join(root, 'data', dataType))

  // @ts-ignore
  return files.reduce((allPosts, postSlug) => {
    const source = fs.readFileSync(path.join(root, 'data', dataType, postSlug), 'utf8')
    const { data } = matter(source)

    return [
      {
        frontMatter: data,
        slug: postSlug.replace('.md', ''),
      },
      ...allPosts,
    ]
  }, [])
}
```

The `gray-matter` package takes the string output from `readFileSync` (which reads a particular markdown file), and spits out an object that nicely separates out your frontmatter from the content. Check out the [repo](https://github.com/jonschlinkert/gray-matter) for a more in-depth explanation.

```tsx
// Input string
'---\ntitle: Front Matter\n---\nThis is content.'

// Output object
{
  content: '\nThis is content.',
  data: {
    title: 'Front Matter'
  }
}
```

### Step 5: Display a list of all your blog posts.

Now back to `blog.tsx` which is what renders when a user visitors `yourdomain.com/blog`. We want this to display a list of all the blog posts in `data > blog`. This is where NextJS's `getStaticProps()` function comes in. Check out the [official docs](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) on what this function does, but essentially, the props generated from this method will be passed to its page component as props during **build** **time**.

In our case, we want this page to show:

- All the posts
- The title for the blog (optional - I use this for meta tags)
- The description of the blog (optional - I use this for meta tags)

```tsx
import { getAllPostsWithFrontMatter } from '@/lib/utils'

export async function getStaticProps() {
  const posts = await getAllPostsWithFrontMatter('blog')

  return {
    props: {
      posts,
      title: 'Blog',
      description: 'Posts on software engineering',
    },
  }
}
```

The other thing we need on this page is the HTML and content we want to display, including a section were we will render a list of the blog posts. Here's an example:

```tsx
import BlogPosts from '@/components/BlogPosts'
import CustomLink from '@/components/CustomLink'
import SiteContainer from '@/components/SiteContainer'
import { getAllPostsWithFrontMatter } from '@/lib/utils'
import { BlogProps } from 'types'

export default function Blog({ posts, title, description }: BlogProps) {
  return (
    <SiteContainer title={title} description={description}>
      <div>
        <section className="blog-posts">
          <p>
            I'm trying to solidify my learnings and help others at the same time by writing these short blog posts. I
            generally write about problems I come across and how I solved them. I'll occassionally also write about my
            personal experiences of navigating my career as a software engineer.
          </p>
          <p>
            If you spot an error, or have any comments, suggestions or questions about what I've written, contact me on
            Twitter <CustomLink href="https://twitter.com/bionicjulia">@bionicjulia</CustomLink> or email{' '}
            <CustomLink href="mailto:hello@bionicjulia.com">hello at bionicjulia.com</CustomLink>. I'd love to hear from
            you. 🤓
          </p>
          <h3>✍🏼 Blog posts on my experience as a software engineer</h3>
          <BlogPosts posts={posts} />
        </section>
      </div>
    </SiteContainer>
  )
}

export async function getStaticProps() {
  const posts = await getAllPostsWithFrontMatter('blog')

  return {
    props: {
      posts,
      title: 'Blog',
      description: 'Posts on software engineering',
    },
  }
}
```

Side notes:

- Don't worry about `SiteContainer`, that's just a layout component that contains all the content across my site, in addition to settting the meta tags for each page.
- Similarly, you can replace `CustomLink` with the standard `Link` component NextJS gives you out of the box.
- You might have noticed I import my components using the `@` symbol. These are shortcuts that NextJS allows you to setup in `tsconfig.json`, but you can just import the components in the usual way. If you want to see my setup, checkout the Appendix below.

The more interesting component here is `BlogPosts` which just renders a list of all the `posts` we pass into it. Note that `posts` is what is returned from the `getStaticProps` function, which itself references the `getAllPostsWithFrontMatter` utility method we created in Step 4. What that function does is to essentially loop through our `data > blog` folder, read all the markdown content in each file and return all of the posts' data in an array, comprising objects with the `frontMatter` and `slug` for each post.

The `BlogPosts` component looks like this:

```tsx
import Link from 'next/link'
import { BlogPostsProps } from 'types'

const BlogPosts = ({ posts }: BlogPostsProps) => {
  return (
    <div className="posts">
      {!posts && <div>No posts!</div>}
      <ul>
        {posts &&
          posts
            .sort(
              (a, b) =>
                new Date(b.frontMatter.publishedDate).getTime() - new Date(a.frontMatter.publishedDate).getTime(),
            )
            .map((post) => {
              return (
                <article key={post.slug} className="post-title">
                  <Link href={{ pathname: `/blog/${post.slug}` }}>
                    <a>{post.frontMatter.title}</a>
                  </Link>{' '}
                  - {post.frontMatter.description}
                  <p>[ {post.frontMatter.tags.join(', ')} ]</p>
                </article>
              )
            })}
      </ul>
    </div>
  )
}

export default BlogPosts
```

Note the use of the `Link` component, which allows us to view each individual blog post when we click on the blog post title (that we determined in the YAML frontmatter in Step 3).

### Step 6: Determine how each individual blog post is rendered.

This is done in `[slug].tsx`, where the first thing we need to do is tell NextJS what all of the relevant paths are for each post. In this case, I want the URL to be `yourdomain.com/blog/post1` where `post1` is the name of the markdown blog post in `data > blog` i.e. `post1.md`.

NextJS allows us to do this with the `getStaticPaths()` function ([official docs](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation)). This returns an array of paths, autogenerated from our markdown file names, along with params we might want to pass along to that path. Similarly to `getStaticProps()` , this is pre-rendered at **build** **time**. The `getFiles` function comes from our utility file in Step 4 (again, I did this for reusability across my various blogs but you can have it in this file if you wish).

```tsx
export async function getStaticPaths() {
  const posts = await getFiles('blog')

  const paths = posts.map((filename: string) => ({
    params: {
      slug: filename.replace(/\.md/, ''),
    },
  }))

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

We also need a `getStaticProps()` function here to pass in the necessary props to this page component, in order to render the frontmatter and markdown body in our UI:

```tsx
export async function getStaticProps({ params }: Params) {
  const { frontMatter, markdownBody } = await getPostBySlug('blog', params.slug)

  return {
    props: {
      frontMatter,
      markdownBody,
    },
  }
}
```

Note that we're using another one of our utility functions as defined in Step 4, where this function is effectively returning all of the post content from the markdown file whose name matches the `slug` argument. `params.slug` comes from the `getStaticPaths()` function above and is what is available from the path params when someone visits `yourdomain.com/blog/post1`.

Still with me? We've just got one other thing to do on this page, and that's to render our markdown body in a way that allows us to highlight our code snippets in the right way. This is where the `react-markdown` and `react-syntax-highlighter` packages come in.

```tsx
import React from 'react'
import ReactMarkdown from 'react-markdown'
import BlogLayout from '@/layouts/BlogLayout'
import { BlogPostProps } from 'types'

const BlogPost = ({ frontMatter, markdownBody }: BlogPostProps) => {
  if (!frontMatter) return <></>

  return (
    <BlogLayout frontMatter={frontMatter}>
      <ReactMarkdown
        allowDangerousHtml={false}
        source={markdownBody}
        renderers={{
          link: (props) => <CustomLink {...props} />,
        }}
      />
    </BlogLayout>
  )
}
```

`BlogLayout` is basically just a UI component, and sets out the styling of a blog post, along with setting the meta data for the blog post page for SEO purposes (which is why I pass in `frontMatter` as props).

`ReactMarkdown` is the markdown component which accepts the following:

- `source` prop whereby we pass in the `markdownBody` which comes from our `getStaticProps()` function above;
- `renderers` which allows us to render React components in replacement of particular node types in the `markdownBody` text. In this case, I'm saying I want all `link` nodes i.e. `a` links to render my `CustomLink` component instead (you can use NextJS's `Link` component here - I just created my own custom component to specifically open external links in a new tab). For a list of node types, check [this out](https://reposhub.com/react/miscellaneous/rexxars-react-markdown.html) (and scroll down to "Node types").

As our blog is a technical blog with a lot of code snippets, we also want to add an additional node for our renderer to recognise, and that's `code`. `CodeBlock` is my custom component that I want to render instead, which is where `SyntaxHighlighter` comes in. The super nice thing about this library is that it allows you to choose specific themes for how your code snippets look. What you currently see for instance, is the `vscDarkPlus` theme that comes from Prism. Check out the [themes here](https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_STYLES_PRISM.MD).

```tsx
import BlogLayout from '@/layouts/BlogLayout'
import { BlogPostProps } from 'types'
import React from 'react'
import ReactMarkdown from 'react-markdown'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism'

type CodeBlockProps = {
  language: string
  value: React.ReactNode
}

const CodeBlock = ({ language, value }: CodeBlockProps) => {
  return (
    <div className="code-block">
      <SyntaxHighlighter language={language} style={vscDarkPlus}>
        {value}
      </SyntaxHighlighter>
    </div>
  )
}

const BlogPost = ({ frontMatter, markdownBody }: BlogPostProps) => {
  if (!frontMatter) return <></>

  return (
    <BlogLayout frontMatter={frontMatter}>
      <ReactMarkdown
        allowDangerousHtml={false}
        source={markdownBody}
        renderers={{
          link: (props) => <CustomLink {...props} />,
          code: CodeBlock,
        }}
      />
    </BlogLayout>
  )
}
```

Alright, so putting it all together, this is what `[slug].tsx` looks like. Please note the `export default BlogPost` right at the bottom!

```tsx
import ReactMarkdown from 'react-markdown'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism'

import BlogLayout from '@/layouts/BlogLayout'
import { BlogPostProps } from 'types'
import { getFiles, getPostBySlug } from '@/lib/utils'
import CustomLink from '@/components/CustomLink'
import React from 'react'
import { Params } from 'next/dist/next-server/server/router'

type CodeBlockProps = {
  language: string
  value: React.ReactNode
}

const CodeBlock = ({ language, value }: CodeBlockProps) => {
  return (
    <div className="code-block">
      <SyntaxHighlighter language={language} style={vscDarkPlus}>
        {value}
      </SyntaxHighlighter>
    </div>
  )
}

const BlogPost = ({ frontMatter, markdownBody }: BlogPostProps) => {
  if (!frontMatter) return <></>

  return (
    <BlogLayout frontMatter={frontMatter}>
      <ReactMarkdown
        allowDangerousHtml={false}
        source={markdownBody}
        renderers={{
          code: CodeBlock,
          link: (props) => <CustomLink {...props} />,
        }}
      />
    </BlogLayout>
  )
}

export async function getStaticProps({ params }: Params) {
  const { frontMatter, markdownBody } = await getPostBySlug('blog', params.slug)

  return {
    props: {
      frontMatter,
      markdownBody,
    },
  }
}

export async function getStaticPaths() {
  const posts = await getFiles('blog')

  const paths = posts.map((filename: string) => ({
    params: {
      slug: filename.replace(/\.md/, ''),
    },
  }))

  return {
    paths,
    fallback: false,
  }
}

export default BlogPost
```

## Conclusion

And that's it! Like I said, a bit fiddly to get everything set up, but now that that's done, it all makes sense and is fairly easy to maintain.

From here, to build, just run `yarn dev`. I signed up for a [Vercel](https://vercel.com/) free account and hooked that up to my website's Github repo. Vercel auto deploys and gives you preview links for branches that you push to Github, so it's super easy to push to staging and production from your command line.

So what do you think? Was this helpful? Anything I could have made clearer? All constructive suggestions are welcome. 😁 Talk to me on [Twitter](https://twitter.com/bionicjulia) or [Instagram](https://instagram.com/bionicjulia) **@bionicjulia**!

## Appendix

### Types

```tsx
export type BlogFrontMatter = {
  title: string
  description: string
  publishedDate: string
  tags: string[]
}

export type BlogLayoutProps = {
  children: React.ReactNode
  frontMatter: BlogFrontMatter
  wordCount: number
  readingTime: string
}

export type BlogPostProps = {
  slug: string
  siteTitle: string
  frontMatter: BlogFrontMatter
  markdownBody: any
  wordCount: number
  readingTime: string
}

export type BlogPostsProps = {
  posts?: BlogPostProps[]
}

export interface BlogProps extends BlogPostsProps {
  title: string
  description: string
}
```

### Setting up shortcut paths in NextJS

In `tsconfig.json` add this to your `compilerOptions` (extend and delete as needed):

```tsx
"paths": {
    "@/components/*": ["components/*"],
    "@/data/*": ["data/*"],
    "@/lib/*": ["lib/*"],
  }
```