Skip to content

Using Markdoc with Eleventy

This article introduces Markdoc, walks through the practicalities of using it with Eleventy, then adds opinionated discussion of when to use Markdoc (and whether you should actually use it with Eleventy).

A brief moment of docs tooling hype

On May 11th 2022, a niche part of tech Twitter briefly got very excited: Stripe open sourced part of their docs tooling! Stripe's docs are often held up as an example of very good documentation, and it's fairly well known (at least in a niche corner of tech Twitter, and the Write the Docs Slack) that they have rolled their own custom tooling, and have dedicated engineering resources for their docs platform.

A closer look revealed that the source of the excitement, Markdoc, is only one piece of the setup: Stripe's markdown parser. It includes some nice features: extend Markdown with tags (unfortunate name choice though), customize the built-in nodes, validate document syntax.

To build a site you need to pair Markdoc with other tools. As a quick experiment, I got it working with Eleventy. In some ways this is a natural pairing: Eleventy is designed to be flexible. Switching out the default Markdown renderer and replacing it with Markdoc was fairly simple.

Using Markdoc with Eleventy

If you don't want to go through the steps yourself, you can clone the repo and follow the steps in the README. The example in the walkthrough is simpler than the repo contents.

Make sure to read the gotchas.

Prerequisites

  • A text editor
  • Node.js and npm installed. The latest LTS version of Node.js should be fine.
  • Some familiarity with Eleventy: for example, this article doesn't go into detail about how the project structure and layouts work.

Project setup

  1. Create a new project:

    1
    2
    3
    mkdir eleventy-markdoc
    cd eleventy-markdoc
    npm init -y
    
  2. Install the dependencies:

    npm i --save-dev @11ty/eleventy @markdoc/markdoc cross-env 
    
  3. Open package.json in your text editor, and update the scripts section:

    1
    2
    3
    4
    5
    6
    7
    "scripts": {
        "start": "npm run dev",
        "dev": "cross-env eleventy --serve",
        "prod": "cross-env eleventy --serve",
        "build": "cross-env eleventy",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    

    We're using cross-env to ensure Windows compatibility.

  4. Create the following files and directories:

    • .eleventy.js in the root of your project.
    • A directory named src containing index.md.
    • _includes/layouts directory in src.
    • A main.njk file in layouts.
  5. Add some example content to main.njk and index.md:

    <!-- In main.njk -->
    <!doctype html>
    <html lang="en">
    <head></head>
    <body>
    
    <main>
    <h1>{{ title }}</h1>
    {% if description %}<h2>{{ description }}</h2>{% endif %}
    
    {{ content | safe }}
    
    </main>
    
    </body>
    
    // In index.md
    ---
    title: Test title
    description: Test description
    layout: layouts/main.njk
    ---
    
    Some very **normal** markdown.
    
    The year is: {% $currentYear %}
    

    This gives us some test content, and a simple layout, to try out.

  6. Set up the basics of the Eleventy configuration in .eleventy.js:

    module.exports = function(eleventyConfig) {
    
        return {
            markdownTemplateEngine: false,
            passthroughFileCopy: true,
            dir: {
                input: "src",
                output: "site"
            }
        }
    };
    

Enabling Markdoc

By default, Eleventy uses markdown-it as its Markdown renderer. To replace markdown-it with Markdoc, you need to override the existing templating language.

  1. Open .eleventy.js in your text editor.
  2. Import Markdoc:
    const Markdoc = require('@markdoc/markdoc'); 
    
  3. Use Eleventy's addExtension() method to replace the default parser for Markdown files:
    1
    2
    3
    4
    5
    6
    7
    8
    eleventyConfig.addExtension("md", {
        compile: function (inputContent, inputPath) {
    
            return (data) => {
                return html;
            }
        }
      });
    
  4. Add Markdoc's parse, transform, and render steps. Your addExtension code should now look like this:
     eleventyConfig.addExtension("md", {
        compile: function (inputContent, inputPath) {
            return (data) => {
                let ast = Markdoc.parse(inputContent);
                let content = Markdoc.transform(ast, markdocConfig);
                let html = Markdoc.renderers.html(content); 
                return html;
            } 
        }
      });
    
  5. At this point, running npm start should work. However, the output will have one error. In index.md we're using a Markdoc variable, {% $currentYear %}. We need to set this up in the Markdoc config. Update your addExtension code to include a config object:
        eleventyConfig.addExtension("md", {
            compile: function (inputContent, inputPath) {
                return (data) => {
                    let ast = Markdoc.parse(inputContent);
                    let markdocConfig = {
                        variables: {
                            currentYear: '2022'
                        }
                    };
                    let content = Markdoc.transform(ast, markdocConfig);
                    let html = Markdoc.renderers.html(content); 
                    return html;
                } 
            }
        }
      });
    
  6. Serve the site locally by running npm start. You should be able to view the site at localhost:8080.

Gotchas

There are some issues to be aware of when using Markdoc with Eleventy. The two big ones are:

  1. Overriding the default Markdown parser opts-out of pre-processing your Markdown files with any other parser. Normally, with Eleventy, you can choose a templating language and pre-process your Markdown files. For example, setting markdownTemplateEngine: "njk" in your config file allows you to use Nunjucks in your Markdown. By default, Eleventy uses Liquid for pre-processing. Enabling Markdoc prevents this step, but (slightly confusingly) the command line output still says it's using Liquid. To avoid confusion, set markdownTemplateEngine: false. Keep in mind that this removes the ability to use custom Eleventy shortcodes and filters: you'll have to rely on Markdoc tags, functions, and node customization.
  2. Markdoc supports frontmatter, but Eleventy strips and processes frontmatter before passing the file contents to the compile function using inputContent. This means you can't use Markdoc's frontmatter functionality. You can still use frontmatter in your layout files (Eleventy can access it as normal), but not directly in your content files (see gotcha no.1).

Frontmatter workaround

You can work around the frontmatter problem by accessing Eleventy's frontmatter in the data object, then assigning it to a Markdoc variable, in order to make it available within your Markdoc content files. For example, to access a field title in the page's frontmatter:

1
2
3
4
5
let markdocConfig = {
    variables: {
        title: data.title
    }
};

Should you even do this?

Warning: this section contains subjective opinions based on very little testing.

The technical aspect

There are some nice things about Markdoc. This review sets out some of the positives. I think built-in validation support is great. If you're using a Node.js-based stack for your docs site, it could be a good option as your Markdown parser.

It's worth noting that it takes you a long way away from standard Markdown syntax: one reason I like being in the Python ecosystem is that PyMdown provides a smoother-feeling extension of Markdown syntax than many of the JavaScript options.

Moreover (and ironically, considering my chosen test case), Eleventy makes Markdoc a less-exciting addition to JavaScript-based docs tooling than it might have been a few years ago. Because Eleventy allows you to use a templating language in Markdown, and create your own shortcodes and filters, a lot of the power of Markdoc is already available. I'd need to experiment more, but at the moment, I'm not convinced Markdoc adds enough to an Eleventy site to be worth losing the power of Nunjucks-in-Markdown.

Of course, not everyone wants to build with Eleventy. Markdoc will let you create custom tags (similar to Eleventy shortcodes) for a React-based site. If you're using Next.js for your docs site, check out Markdoc's own site as an example. I'm not familiar enough with all the React-based static site options, but perhaps the value of Markdoc is greater here?

The human aspect

Using Markdoc won't get you docs like Stripe. To do that you need a skilled, dedicated team, and either dedicated engineering resource, or very technical tech writers, who can build and maintain such custom tooling.

If you're not Stripe, this is probably a bad idea.

It's' usually better to use off-the-shelf tooling, even if it means a few compromises, than to roll your own: building your own docs tooling means building an entire product, which is bad enough. But it also means maintaining and extending an enire product. If your docs team includes developers, designers, and project managers - go for it, and please share what you build. But one or two very busy writers, already snowed under just keeping up with content? Be kind to yourself, find a static site generator you can configure easily, and a theme you like.

Wrap up

I probably won't be using Markdoc any time soon: I'm a lone writer at a startup with impressive dev pace, I need tooling that gives me maximum power with minimum effort - so MkDocs and Material for MkDocs. But playing around with docs as code tooling is my happy place, and I'd genuinely love to hear what other people do with Markdoc: if you do something fun, come and tell me about it on Twitter.

Back to top