using Markdown as Next.js pages

In Next.js, each page in your website is represented by a javascript file that serves as its entry point. These pages get pre-rendered at build time, or on the server side if they're dynamic, which has the advantage of being easier to index by spiders and also to be immediately ready to display on the client as no startup code needs to run before things appear.

This is great but, of course, I don't want to write my posts in javascript! I write posts in Markdown as it is a great compromise between being readable and providing rich encoding.

What I ended up doing was to write a webpack plugin that lets me use my markdown content in place of the usual javascript files. The plugin simply takes a .md file as input and generates javascript that next.js can then compile like it expects to. In the end, it simply is a matter of modifying next.config.js to handle the conversion:

config.module.rules.push({
  test: /\.md$/,
  include: pagesFolder,
  use: [
    ...loaders,
    {
      loader:path.resolve('lib/post-loader.js'),
      options:{}
    }
  ]
})

  • pagesFolder is the path to the /pages folder that next.js uses to build the tree structure ; markdown present elsewhere should be left alone
  • loaders is the set of plugins already registered to process javascript, I should be able to make this implicit but haven't figured it out yet (any advice would be welcome)

The code in lib/post-loader.js then, given the Markdown content, inserts it into code like this :

import PostPage from '~/components/post'
import {getPostThread} from '~/lib/compile-posts'
<<<importSupportScript(md)>>>

const Page = (props)=> (
  <PostPage
    slug={<<<slug>>>}
    content={props.content}
    script={MDScript}
    threadPosts={props.threadPosts}
    ${coverSize(md)}
  />
)

export async function getStaticProps(context) {
  return {
    props: {
      content: <<<escapedSource>>>,
      threadPosts: getPostThread('posts',<<<thread>>>,['slug', 'title', 'date'])
    },
  }
}

export default Page

  • escapedSource is the markdown content with back quotes escaped so it's safe to include in source code
  • slug is the name of the markdown file
  • thread is the thread name if there is one in the front matter of the post
  • coverSize(md) fetches the size of the cover image if there is one in the front matter
  • importSupportScript(md) lets me import more code (as MDScript) for pages that include special features, but I'll talk more about this in a future post.

In a previous version of this post, I was saying that writing this blog works great as a code review. I was even more right than I expected as it made me realize I had a wrong vision of getStaticProps. I was looking at generating everything in my plugin to make the generated code simpler, which worked, but it wasn't the best strategy as I realized when working on translations...

To see this in context, you can check out the code on gitHub !

In future posts I'll talk about implementing i18n (Internationalization) which I have almost finished and, as promised above, I'll talk about how posts with special features are put together.

Jérôme Muffat-Méridol
January 27, 2021

Behind the scenes

  1. open-sourcing myself
  2. using Markdown as Next.js pages
  3. Posting both in French and English