How to use markdown rather than code to create pages in a Next.js project
Tags: react, javascript, nextjs, tutorial
Published: 2020-09-25 16:39:49

**Originally titled: Using Markdown and the Remark converter to make a dynamic and maintainable Next.js project**

*This blog part one of a two part extension to my rebuild of the Wallis Consultancy website. It documents how to use Markdown files with Next.js to dynamically create pages at build time and use Netlify CMS to make them editable without having to download the source code to your local machine.*

The finished website (hosted on GitHub Pages): https://wallisconsultancy.co.uk
The source code: https://github.com/james-wallis/wallisconsultancy

## Background

When I rebuilt wallisconsultancy.co.uk using Next.js a couple of months ago, one of the reasons for doing so was to make it more maintainable than it was when it was implemented via PHP and ran in a Docker container. While on a whole I achieved this goal, making changes to the content is still overcomplicated and means modifying a React.js file in the `pages` directory which Next.js uses to separate pages.

Writing a page using Markdown leads to a much better experience than using React.js. For starters, you write in plain text and don't need to worry about opening tags and closing tags or classNames for styling, you can just focus on the content. 

Therefore in this blog I will demonstrate how to configure a Next.js website to use Markdown for the content of each page and dynamic routes so that you can use a generic layout for each page. By combining these, we will end up with a much more maintainable website that has only two files in the pages directory and has content that is changed solely by editing a Markdown file on a per page basis.

## Plan

To use the following to create a website which gets it's content from Markdown files:
* [Next.js dynamic routes](https://nextjs.org/docs/routing/dynamic-routes).
* [Remark-parse](https://github.com/remarkjs/remark) with the [remark-react](https://github.com/remarkjs/remark-html) plugin to transform the Markdown to React. Note: I'm using remark-react rather than remark-html so that I don't need to use `dangerouslySetInnerHTML`, read more about this on the [remark-react GitHub](https://github.com/remarkjs/remark-react#remark-react).

We'll creating two pages (in the `pages` directory):
1. `index.js` - for the home page
2. `[slug].js` - for every other page

AFAIK it's not possible to have `/` as a dynamic route through Next.js - if you know otherwise reach out and I'll append this post!

Note: It is possible to do "catch all routes" which should allow you to do subpaths in URLs but for this tutorial I'll only explain how to do top level paths ('/', '/about'). To read more about catch all routes checkout [the Next.js docs](https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes).


## Let's do it

We need to install some packages so that we can convert the Markdown to HTML. Run `npm i -s unified remark-parse remark-react gray-matter`.

Next we need to create some helper functions that our Next.js application can call to get a list of all the Markdown files in our directory and the contents files. Create a `utils` file for these functions and paste in the following JavaScript. I normally put these in a `lib` directory and have called the file `markdown.js` ([view on GitHub](https://github.com/james-wallis/wallisconsultancy/blob/master/lib/markdown.js)).

```javascript
import fs from 'fs';
import { join } from 'path';
import matter from 'gray-matter';

/**
 * _pages and _pages/dynamic directory where the markdown content will live
 * _pages will have the home.md (aka index or /)
 * _pages/dynamic will be home to all other pages (aka [slug].js)
 */
const pagesDirectory = join(process.cwd(), '_pages');
const dynamicPagesDirectory = join(pagesDirectory, 'dynamic');

/**
 * Gets all the files (slugs) in a directory
 */
export function getSlugsFromDirectory(dir) {
  return fs.readdirSync(dir);
}

/**
 * Gets the contents of a file
 * The gray-matter (metadata at the top of the file) will be
 * added to the item object, the content will be in
 * item.content and the file name (slug) will be in item.slug.
 */
export function getBySlug(dir, slug, fields = []) {
  const realSlug = slug.replace(/\.md$/, '');
  const fullPath = join(dir, `${realSlug}.md`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');
  const { data, content } = matter(fileContents);

  const items = {};

  // Ensure only the minimal needed data is exposed
  fields.forEach((field) => {
    if (field === 'slug') {
      items[field] = realSlug;
    }
    if (field === 'content') {
      items[field] = content;
    }

    if (data[field]) {
      items[field] = data[field];
    }
  });

  return items;
}

/**
 * Returns contents of a page in the _pages directory
 */
export function getPageContentBySlug(slug, fields = []) {
  return getBySlug(pagesDirectory, slug, fields);
}

/**
 * Returns contents of a page in the _pages/dynamic directory
 */
export function getDynamicPageContentBySlug(slug, fields = []) {
  return getBySlug(dynamicPagesDirectory, slug, fields);
}

/**
 * Returns a list of all the pages in the _pages/dynamic directory
 */
export function getAllDynamicPages(fields = []) {
  const slugs = getSlugsFromDirectory(dynamicPagesDirectory);
  const pages = slugs.map((slug) => getDynamicPageContentBySlug(slug, fields));
  return pages;
}
```

Copy that JavaScript into your project and then we'll be ready to create our dynamic page!

### Creating the dynamic page ([slug].js)

Using Next.js dynamic pages, we will add functionality to create a separate page for each Markdown file in the `_pages/dynamic` directory while only needing to create a single file in the `pages` directory.

To do this we will need to use a couple of Next.js functions:
1. [`getStaticPaths`](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation): This function is used to tell Next.js what URL paths are going to be rendered, so in this function we will call `getAllDynamicPages` from the `markdown.js` file above.
2. [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation): This function is used to get additional props for a page at build time, so in this function will receive the `slug` (file path) to render and we will pass it into the `getDynamicPageContentBySlug` to get the metadata and contents for a page.

Create a page called `[slug].js` in your `pages` directory with the following contents:

```JavaScript
import PrintMarkdown from '../components/markdown/printMarkdown';
import { getDynamicPageContentBySlug, getAllDynamicPages } from '../lib/markdown';

export default function DynamicPage({ page }) {
  const {
    title,
    description,
    slug,
    content,
  } = page;
  
  return (
    <div>
      <h1>{title}</h1>
      <h2>{description}</h2>
      {/* we'll go into the PrintMarkdown component later */}
      <PrintMarkdown markdown={content} />
    </div>
  );
}

export async function getStaticProps({ params }) {
  const { slug } = params;

  // Pass in the fields that we want to get
  const page = getDynamicPageContentBySlug(slug, [
    'title',
    'description',
    'slug',
    'content',
  ]);

  return {
    props: {
      page: {
        ...page,
      },
    },
  };
}

export async function getStaticPaths() {
  const posts = getAllDynamicPages(['slug']);
  const paths = posts.map(({ slug }) => ({
    params: {
      slug,
    },
  }));
  return {
    paths,
    fallback: false,
  };
}
```

and create a Markdown (`.md`) file in the `_pages/dynamic` directory at the top level of your project containing the following Markdown:

```markdown
---
title: "Hello dynamic world!"
description: "My first dynamic Page"
---

# Heading 1

A paragraph with some **bold text**.
```

If you run that, Next.js will throw an error because the `PrintMarkdown` component doesn't exist.

### PrintMarkdown Component

Let's create a component which can turn out Markdown into React!

Create a file in your components directory and call it `PrintMarkdown` with the following contents:

```JavaScript
import unified from 'unified';
import parse from 'remark-parse';
import remark2react from 'remark-react';
import markdownStyles from './markdown-styles.module.css';

export default function PrintMarkdown({ markdown }) {

  // Convert the Markdown into React
  const content = unified()
    .use(parse)
    .use(remark2react)
    .processSync(markdown).result;

  return (
    <div className={markdownStyles.markdown}>
      {content}
    </div>
  );
}
```

This file will turn the Markdown into React and add it to the DOM. It uses [css-modules](https://github.com/css-modules/css-modules) to style the React. I won't go into what styles I use but you can find the Wallis Consultancy styles [on GitHub](https://github.com/james-wallis/wallisconsultancy/blob/master/components/markdown/markdown-styles.module.css).

Once you've added all of the above, you should be able to run your Next.js project and see a page displaying your title, description and markdown.

*Note: Any links that you use to navigate to other parts of your website in the Markdown will be converted to `<a>` tags rather than Next.js `<Link>` tags. [I've written a short post demonstrating how to do this](https://dev.to/jameswallis/how-to-use-the-remark-markdown-converters-with-next-js-projects-a8a).*

We're almost done creating out dynamic website - you should be able to create more Markdown files in the `_pages/dynamic` directory and then reach them in your browser when Next.js is running. To finish, we just need to create the index page (`/`) separately to the dynamic pages as you will find that if you create a file with the name `index.md` it will not work for the home page (the URL will be `/index`, not great).

### The Index page (`/`)

The index page will be like the dynamic page above but instead of using the `getStaticPaths` Next.js function we will hardcode the `slug` for `getPageContentBySlug` function call in `getStaticProps` to `home` so that it reads the Markdown file `_pages/home.md` (not `_pages/dynamic` as we're calling `getPageContentBySlug`).

First create the Markdown file in the `_pages` directory and give it the contents:

```Markdown
---
title: Home
description: "Your home page"
---

# Home page

This is your home page
```

Next, create a new file in your `pages` directory called `index.js` and give it the following content:

```JavaScript
import PrintMarkdown from '../components/markdown/printMarkdown';
import { getDynamicPageContentBySlug, getAllDynamicPages } from '../lib/markdown';

export default function IndexPage({ page }) {
  const {
    title,
    description,
    slug,
    content,
  } = page;
  
  return (
    <div>
      <h1>{title}</h1>
      <h2>{description}</h2>
      <PrintMarkdown markdown={content} />
    </div>
  );
}

export async function getStaticProps() {
  // Here we're using the getPageContentBySlug 
  // as opposed to getDynamicPageContentBySlug
  // We're also passing in the string 'home' to tell it 
  // we want to use the _pages/home.md file for the 
  // page props
  const page = getPageContentBySlug('home', [
    'title',
    'description',
    'slug',
    'content',
  ]);

  return {
    props: {
      page: {
        ...page,
      },
    },
  };
}

```

Once you've created and populated those two files, your home page should be available!

## Round Up

In this blog we've configured our Next.js application to use build it's routes using Markdown files and we've made it dynamic so that we only need to maintain one file! Okay, two files ([slug].js, and index.js) but the root path is an exception (if you can make this dynamic too, leave a comment and I'll update the tutorial!).

In second part of this two part series, I'll add Netlify CMS to the Wallis Consultancy website so that it can be used to create and modify pages dynamically on the website.

**Remember:** *The links that the remark-react project creates will be an `<a>` tag and not the Next.js `<Link>` tag. To make them use `<Link>` tags for local links and `<a>` for external links you'll need to use a custom component - [I've written a short post demonstrating how to do this](https://dev.to/jameswallis/how-to-use-the-remark-markdown-converters-with-next-js-projects-a8a).*