N00b's Programming Machine


Need private packages and team management tools?Check out npm Orgs. »


2.0.3 • Public • Published


Support for markdown content in your nuxt.js site.

Note: This is a fork of Nuxtent with some additional features.


Nuxtdown allows you to use markdown files as content in your Nuxt.js application. It combines the ease-of-use of markdown for writing content, with the power of Nuxt.js for building advanced sites and web applications.

In combination with Nuxt's generate mode to generate a static site, Nuxtdown allows you to seamlessly port your site's content from Jekyll, Hugo, or other static site generators.


  • Simple configuration
  • Allows you to override settings for the markdown parser (markdown-it), and use its plugins
  • Support both blog posts or similar flat content structures, and a nested hierarchy of markdown content
  • Adds useful info to your markdown page's meta info, including:
    • Any data your specify in the config file
    • A breadcrumbs trail for hierarchical markdown content
    • (the info for) a table of contents
  • Support for using vue components inside your markdown content
  • Adds the $content helper to Nuxt to allow your to access your markdown content and metadata inside Nuxt pages

Quick Start

If you already have a site, follow these three steps:

  • Step 1: Pick your mode
    • Single directory of markdown files --- typically blog posts or similar
    • Nested structure of markdown files --- typically documentation or similar
  • Step 2: Place your files in the content directory`
  • Step 3: Configure your content

That's it, you're done.


npm install nuxtdown --save

Then, under nuxt.config.js install the module:

modules: [


Nuxtdown looks for its configuration in the nuxtdown.config.js file in the root of your application.

Content configuration

Here are the configuration options for your content:

Name Description Default
page The Nuxt page that will serve the dynamic routes to your content.
permalink The permalink under which your content will be available.
isPost Whether your markdown files have YYYY-MM-DD-slug.mg format or not. true
data An object of key/value pairs that will get added to your frontmatter. false
breadcrumbs Whether to generate a breadcrumb trail or not. false
toc Controls the (optional) generate of a table of contents. (see below) false
markdown Options for the markdown parser. (see below)

Each of these apply to a folder in the content directory. You can have multiple, and different, configurations side by side.


This is the Nuxt page that will serve the dynamic routes to your content. As such, it's always a file somewhere in app/pages that starts with an underscore (as that's how Nuxt knows it's for dynamic routes)

This config option has no default and must be configured.


The permalink under which the content will be available in the browser.

Note: This will be different from the link under which we access it in the content api (see below).

The permalink configuration can take a number of placeholdes:

  • :slug : A slugified version of your filename (without the date for posts)
  • :year : Year of the post (only relevant when isPost is true)
  • :month : Month of the post (only relevant when isPost is true)
  • :day : Day of the post (only relevant when isPost is true)
  • :section : The subdirectory within your content directory. See below.

The :section part of your permalink deserves a bit more attention. It is the subdirectory path from the top of your content group to your markdown file.

For a markdown file in the root of our content folder, the section wil be /. But for subdir1/subdir2/somepage.md the section will be subdir1/subdir2.

This config option has no default and must be configured.


Whether or not your markdown content are posts.

When this is true (the default) your markdown filenames should start with YYYY-MM-DD- which will be used as the post date.

When this is false your markdown files should just have names like somepage.md.


An object of key/value pairs that will get added to the frontmatter/metadata of all your markdown files for this content group.


If set to true this will include metadata to build a trail from your content trunk to the content leaf.

It will not include trunk or leaf, but only the intermediate steps. For example:

  • If our content is in folder docs:
    • docs/about.md will have no breadcrumbs, as docs is the trunk and docs/about is the leaf
    • docs/example/something.md will have 1 breadcrumb for docs/example

Here is a real-world sample of the data generated by the breadcrumbs option for a docs/patterns/simon page:

     layout: "cards"
     title: "Patterns"
     permalink: "/patterns/"
     nocomments: true
     contentLocale: "en"
   permalink: "/docs/patterns"


Whether to generate (the metadata for) a table of contents or not. Defaults to false, no table of contents.

This uses the markdown-it-anchor plugin for markdown-it under the hood.

toc as an integer

If you set toc to an integer, the anchors plugin will be configured as follows:

Name Description
level The value your set toc to in your content configuration
permalink true
permalinkClass nuxtdown-toc
permalinkSymbol 🔗
toc as an object

If you don't like these defaults, you can pass your own configuration object to configure the plugin. See the official documentation

Excluding titles from your table of contents

To prevent a title from being added to the toc, give it the .notoc CSS class.

Tip: You can use the markdown-it-attrs plugin to do this in your markdown.

Here is a real-world sample of the data generated by the toc option for a blog post:

  topLevel: 2
      level: 2
      title: "Looking back at september, and a bit of august"
      link: "#looking-back-at-september-and-a-bit-of-august"
      level: 3
      title: "My name is Simon, and I’m complicated"
      link: "#my-name-is-simon-and-im-complicated"
      level: 4
      title: "What’s your problem Simon?"
      link: "#whats-your-problem-simon"
      level: 4
      title: "Is it time for an overhaul?"
      link: "#is-it-time-for-an-overhaul"
      level: 2
      title: "Dealing with email delivery issues"
      link: "#dealing-with-email-delivery-issues"

The output will contain a topLevel attribute that tells you what the highest level of heading is that is included in your toc. This allows you to build a hierarchical table of contents where the top level is the actual highest level of heading in your content.


This allows you to configure the markdown-it markdown-it markdown parser.

It takes a configuration object that allows you to configure the parser in three ways:

  • extend the default options passed to the parser
  • Add plugins to the parser.
  • customize the parser after it was created.

Here's an example that does all three. It uses extend to change the highlight function. Uses plugins to load the markdown-it-video plugin, and uses customize to add onion as a TLD to linkify.

markdown: {
  extend(config) {
    config.highlight = (code, lang) => {
      return `<pre class="language-${lang}"><code class="language-${lang}">${Prism.highlight(code, Prism.languages[lang] || Prism.languages.markup)}</code></pre>`
  plugins: {
    video: require('markdown-it-video')
  customize(parser) {


Here's an example that covers most use cases:

Note that the path doesn't have to be a top-level directory. It can also be something like docs/translations/fr

// nuxtdown.config.js
module.exports = {
  content: [
    // My blog posts are in content/blog
    ['blog', {
      page: '/blog/_blogpost',
      permalink: '/blog/:slug'
    // My documentation is in content/docs
    ['docs', {
      page: '/docs/_page',
      permalink: '/docs/:section*/:slug',
      isPost: false,
      data: { 
        generatedBy: 'Nuxt with the nuxtdown module' 
      breadcrumbs: true,
      toc: 1
      markdown: {
        plugins: {
          toc: {
            permalinkClass: 'nuxtdown-toc',
            permalinkSymbol: ''
          attrs: require('markdown-it-attrs'),
          figures: [require('markdown-it-implicit-figures'), { figcaption: true }],
          video: require('markdown-it-video')
  api: {
    baseURL: 'http://localhost:3000',
    browserBaseURL: process.env.FREESEWING_BROWSER_BASE_URL

We have two content groups:

  • The blog directory holds posts (remember, isPost defaults to true)
    • Their dynamic routes will be served by the page approot/pages/blog/_blogpost
    • Their permalink will be /blog/:slug
  • The docs directory holds regular markdown files
    • Their dynamic routes will be served by the page approot/pages/docs/_blogpost
    • Their permalink will be /docs/:section*/:slug
    • These are not posts
    • We want to inject generatedBy into the frontmatter
    • We want a breadcumbs trail to be generated
    • We want the elements for a table of contents to be generated
    • We want to override some of the settings of the markdown parser:
      • Change the class for anchors
      • Change the symbol used for anchors
      • Load three extra plugins for the markdown parser

In this example, our content direcory is structured like this:

  • Nuxt app root
    • content
      • blog
        • 2017-11-14-hello-world.md
        • 2017-12-14-happy-holidays.md
        • 2018-01-14-pizza-party.md
        • 2018-03-21-when-in-rome.md
      • docs
        • about.md
        • examples
          • building-a-pizza-oven.md
          • index.md
          • pizza-from-scratch.md
        • index.md
        • license.md
        • tutorial
          • index.md
          • part-1.md
          • part-2.md
          • part-3.md

Which would lead to this permalink structure on our website:

Requests for a blog posts are served by the blog/_postpost.vue page. Inside it, we have this:

  asyncData: async function ({ app, route }) {
    return { post: await app.$content('/en/blog').get(route.path)}

Note that what we are fetching from the content-api is content-group+permalink, like this:

/content-api/ section permalink

For example, a blog post in section blog with permalink /blog/hello-world will be fetched in async from /content-api/blog/blog/hello-world

API configuration

Apart from the content, you can (and should) configure how to access the content api in nuxtdown.config.js. Here's an example:

    api: {
      baseURL: 'http://localhost:3000',
      browserBaseURL: process.env.FREESEWING_BROWSER_BASE_URL


The url to the content-api used by node, and by the browser in development.

This is probably going to be http://localhost:3000


The url to the content-api used by the browser in production.

Something like https://my-website.com/content-api

In the example above, we use an environment variable for this to differentiate between development and production environments.


MIT License

Copyright (c) 2017 Nuxt Community


npm i nuxtdown

Downloadsweekly downloads










last publish


  • avatar
Report a vulnerability