17

How can I import markdown files as strings in Next.js to work on client and server side?

5 Answers 5

16

You can configure your Next.js webpack loaders to load markdown files and return them as strings, for example:

docs/home.md

# Home

This is my **awesome** home!

pages/index.js

import React from 'react';
import markdown from '../docs/home.md';

export default () => {
  return (
    <div>
      <pre>{markdown}</pre>
      <small><i>Import and render markdown using Next.js</i></small>
    </div>
  );
};

package.json

{
  "name": "example",
  "version": "1.0.0",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "file-loader": "^1.1.6",
    "next": "^4.2.1",
    "raw-loader": "^0.5.1",
    "react": "^16.2.0",
    "react-dom": "^16.2.0"
  }
}

next.config.js

module.exports = {
  webpack: (config) => {
    return Object.assign({}, config, {
      externals: Object.assign({}, config.externals, {
        fs: 'fs',
      }),
      module: Object.assign({}, config.module, {
        rules: config.module.rules.concat([
          {
            test: /\.md$/,
            loader: 'emit-file-loader',
            options: {
              name: 'dist/[path][name].[ext]',
            },
          },
          {
            test: /\.md$/,
            loader: 'raw-loader',
          }
        ]),
      }),
    });
  }
};

When running:

$ npm run dev

Something like this would appear:

example preview

With the markdown string you can do whatever you would like. For example, process it with marksy.

Sign up to request clarification or add additional context in comments.

Comments

15

Updates: emit-file-loader not strictly required, raw-loader deprecated in favor of asset modules

Some updates to https://stackoverflow.com/a/47954368/895245 possibly due to newer developments:

  • asset modules superseded raw-loader: https://stackoverflow.com/a/47954368/895245 No need to install any extra packages for that anyomore
  • emit-file-loader does not seem necessary anymore, not sure if it was needed, or if it is for something more specialized

so we can simplify things slightly to:

pages/index.js

import world from '../world.md'

export default function IndexPage() {
  return <div>hello {world}</div>
}

next.config.js

module.exports = {
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    config.module.rules.push(
      {
        test: /\.md$/,
        // This is the asset module.
        type: 'asset/source',
      }
    )
    return config
  },
}

package.json

{
  "name": "test",
  "version": "1.0.0",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "12.2.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  }
}

world.md

my md world

Getting it to work from Typescript

Unfortunately both raw-loader and asset modules require a bit more work from typescript: https://github.com/webpack-contrib/raw-loader/issues/56#issuecomment-423640398 You have to create a file:

global.d.ts

declare module '*.md'

Otherwise the import will fail with:

Type error: Cannot find module '../world.md' or its corresponding type declarations.

Full example:

global.d.ts

declare module '*.md'

pages/index.tsx

import world from '../world.md'

export default function IndexPage() {
  const what: string = 'my'
  // Would fail on build as desired.
  // const what2: int = 'world2'
  return <div>hello {what} {world}</div>
}

next.config.js

module.exports = {
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    config.module.rules.push(
      {
        test: /\.md$/,
        type: 'asset/source',
      }
    )
    return config
  },
}

package.json

{
  "private": true,
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "type-check": "tsc"
  },
  "dependencies": {
    "next": "v12.2.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@types/node": "12.12.21",
    "@types/react": "17.0.2",
    "@types/react-dom": "17.0.1",
    "typescript": "4.0"
  }
}

world.md

my md world

5 Comments

Where does the global.d.ts go? I followed these steps but with .bbc instead of .md and I'm still getting "Unknown module type This module doesn't have an associated type. Use a known file extension, or register a loader for it."
@Sollace everything on project toplevel. Hmm, so it works with .md but fails with .bbc? Did you change .md to .bcc everywhere else in the other files as well?
Yeah. There's only two places that you have the extension, right? One in the config and one in the import. I did double-check today and it was wrong in the global.d.ts but it still doesn't work after correcting it.
I'll note I never tried it with md as I don't intend on using it with markdown.
Okay, I got it to work but only by: A: Using the experimental turbo option, and B: Installing raw-loader nextjs.org/docs/app/api-reference/config/next-config-js/…
9

Just install raw-loader

npm install --save raw-loader

then edit your next.config.js

webpack: (config) => {
  config.module.rules.push({
    test: /\.md$/,
    use: 'raw-loader',
  });
  return config;
},

3 Comments

Selected answer did not work for me. This did though!
npm install --save or --save-dev? I think it should be --save-dev
8

Quicker and "Next.js way" is to use plugin next-mdx

Documentation: https://github.com/vercel/next.js/tree/canary/packages/next-mdx

// next.config.js
const withMDX = require('@zeit/next-mdx')({
  extension: /\.mdx?$/
})
module.exports = withMDX({
  pageExtensions: ['js', 'jsx', 'mdx']
})

1 Comment

1

The accepted solution didn't work for me, so here's more details on how I got it working with next.js 15 and turbopack.

  1. You need to install raw-loader (asset/source wasn't recognised as a valid loader)
npm install raw-loader --save-dev
  1. You add a global.d.ts file like so:
declare module '*.md' {
    const content: string;
    export default content;
}
  1. In your next.config.js file, you add the rule for turbopack:
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    turbo: {
      rules: {
        '*.md': {
          loaders: ['raw-loader'],
          as: '*.js'
        }
      }
    }
  }
};

export default nextConfig;

After that you can import and use your files as you would normally.

import introduction from "./introduction.md";

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.