Skip to content

Using Markdoc with Next.js

Using the @markdoc/next.js package/plugin allows you to create custom .md and .mdoc pages in your Next.js apps, and automatically render them with Markdoc.

To get started right away, check out this starter repo. The quickest way to deploy your own version of the starter is by deploying it with Vercel or Netlify by clicking one of the buttons below.

Deploy with Vercel Deploy to Netlify


This guide assumes that you already have Next.js installed. If you're starting from scratch, follow these steps for getting started with Next.js.

Follow these steps to get started with @markdoc/next.js.

  1. Install @markdoc/next.js:

    npm install @markdoc/next.js
  2. Update your next.config.js

    const withMarkdoc = require('@markdoc/next.js');
    module.exports = withMarkdoc(/* [options](#options) */)({
      pageExtensions: ['md', 'mdoc', 'js', 'jsx', 'ts', 'tsx']
  3. Create a new .md file in within /pages/, such as

    ├── _app.js
    ├── docs
    │   └──
  4. Add some Markdoc to your file:

    title: Get started with Markdoc
    description: How to get started with Markdoc
    # Get started with Markdoc

Or, clone this starter repo and follow the directions in the README.


You can pass options to withMarkdoc to adjust how the plugin behaves.

schemaPathstringPath to your Markdoc schema folder. See schema customization.
mode'static' | 'server'Determines whether the generated Markdoc pages use getStaticProps or getServerSideProps.

For example, this is how you set the mode to static to pre-render the page at build time using the props returned by getStaticProps:

module.exports = withMarkdoc({ mode: 'static' })({
  pageExtensions: // [...](

Schema customization

You can define your Markdoc schema by creating a /markdoc/ directory at the root of your project. This is where custom nodes, tags, and functions are defined.

├── components
│   ├── ...
│   └── Link.js
├── markdoc
│   ├── functions.js
│   ├── nodes
│   │   ├── ...
│   │   ├── link.markdoc.js
│   │   └── index.js
│   └── tags
│       ├── ...
│       └── index.js
├── pages
│   ├── _app.js
│   └──
└── next.config.js

You can choose the import location for your schema by passing the schemaPath option to withMarkdoc:

module.exports = withMarkdoc({ schemaPath: './path/to/your/markdoc/schema' })({
  pageExtensions: // [...](


You register custom tags by exporting an object from /markdoc/tags.js (or /markdoc/tags/index.js). In this example, the tag name is button. The render field tells Markdoc to render a Button React component whenever the {% button %} tag is used.

// markdoc/tags.js

import { Button } from '../components/Button';

export const button = {
  render: Button,
  attributes: {
    href: {
      type: String

If you want to use kebab case for your tag names, you can export an object like:

// markdoc/tags.js

export default {
  'special-button': {
    render: SpecialButton,
    attributes: {
      href: {
        type: String


Custom node registrations are almost identical to tags, except you create a /markdoc/nodes.js file instead, for example:

// markdoc/nodes.js

import { Link } from 'next/link';

export const link = {
  render: Link,
  attributes: {
    href: {
      type: String

This example overrides the default link node.


Custom functions registrations are almost identical to tags and nodes, except you create a /markdoc/functions.js file instead, for example:

// markdoc/functions.js

export const upper = {
  transform(parameters) {
    const string = parameters[0];

    return typeof string === 'string' ? string.toUpperCase() : string;


If you want more control over your config object, you can create a /markdoc/config.js file and export the full config object. This allows you to extend your config with more data, like records or utility functions.

// markdoc/config.js

import tags from './tags';
import nodes from './nodes';
import functions from './functions';

export default {
  // add other stuff here


Markdoc is frontmatter agnostic, however, @markdoc/next.js uses YAML as its frontmatter language. You can access the frontmatter object within your _app.js under pageProps.markdoc.frontmatter, or in your content using the $markdoc.frontmatter variable.

For example:

title: Using the Next.js plugin
description: Integrate Markdoc into your Next.js app

# {% $markdoc.frontmatter.title %}


Partials automatically load from the /markdoc/partials/ directory. For example:

{% partial file="" /%}

would load and render the contents of markdoc/partials/


To create a custom layout for each of your Markdown/Markdoc files, wrap your Component within your _app.js, for example:

// pages/_app.js

import Layout from '../components/Layout';

export default function App({ Component, pageProps }) {
  return (
    <Layout frontmatter={pageProps.markdoc.frontmatter}>
      <Component {...pageProps} />

Built-in Next.js tags

Next.js Markdoc provides custom tags out-of-the-box that you can add to your schema. To include them, export them by name in your schema directory (for example, /markdoc/). For example:

// markdoc/tags/Next.markdoc.js

export { comment, head, link, script } from '@markdoc/next.js/tags';

// or

export * from '@markdoc/next.js/tags';

After you export the components, you can use them with the corresponding tags in your Markdoc files.


Renders nothing, similar to code comments. Use this to document the content within a Markdoc file.

{% comment %}
Your comment goes here
{% /comment %}


Renders a Next.js Head component. You can use this to add stuff to the <head> of your page.


You need to create and register your own tags for meta, title, and so on.

{% head %}

Add custom `title` and `meta` tags here…

{% /head %}


Renders a Next.js Link component. Requires passing an href attribute.

{% link href="/docs/getting-started" %}
Getting started
{% /link %}


Renders a Next.js Script component. Requires passing a src attribute.

{% script src="" /%}