10

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

Romel Pérez
  • 759
  • 1
  • 6
  • 19

4 Answers4

15

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.

Romel Pérez
  • 759
  • 1
  • 6
  • 19
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;
},
Jonathan.Brink
  • 23,757
  • 20
  • 73
  • 115
sidonaldson
  • 24,431
  • 10
  • 56
  • 61
7

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']
})
icc97
  • 11,395
  • 8
  • 76
  • 90
piecioshka
  • 4,752
  • 2
  • 20
  • 29
3

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
Justin Hammond
  • 595
  • 8
  • 20
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985