Noncommital Premarital Mischief

    next-mdx
    TypeScript icon, indicating that this package has built-in type declarations

    0.6.2 • Public • Published

    next-mdx

    Test License PRs welcome! Follow @shadcn

    next-mdx provides a set of helper functions for fetching and rendering local MDX files. It handles relational data, supports custom components, TypeScript ready and is really fast.

    next-mdx is great for building mdx-powered pages, multi-user blogs, category pages..etc.

    next-mdx-relational-data

    TLDR

    Table of Contents

    Demo

    https://next-mdx-example.vercel.app

    Quick Start

    Learn how next-mdx works by looking at examples.

    1. Go to example-page
    2. Open next-mdx.json to see the sample configuration.
    3. Open pages/[[...slug]].tsx to see how MDX files are fetched and rendered.
    4. See types/index.d.ts for TypeScript.

    Examples

    Click to expand examples.

    next-mdx.json
    {
      "post": {
        "contentPath": "content/posts",
        "basePath": "/blog",
        "sortBy": "date",
        "sortOrder": "desc"
      },
    }
    pages/posts/[...slug].jsx
    import { useHydrate } from "next-mdx/client"
    import { getMdxNode, getMdxPaths } from "next-mdx/server"
    
    export default function PostPage({ post }) {
    const content = useHydrate(post)
    
      return (
        <article>
          <h1 variant="heading.title">{post.frontMatter.title}</h1>
          {post.frontMatter.excerpt ? (
            <p variant="text.lead" mt="4">
              {post.frontMatter.excerpt}
            </p>
          ) : null}
          <hr />
          {content}
        </article>
      )
    
    }
    
    export async function getStaticPaths() {
    return {
    paths: await getMdxPaths("post"),
    fallback: false,
    }
    }
    
    export async function getStaticProps(context) {
    const post = await getMdxNode("post", context)
    
      if (!post) {
        return {
          notFound: true,
        }
      }
    
      return {
        props: {
          post,
        },
      }
    
    }

    Installation

    
    npm i --save next-mdx
    
    

    Configuration

    Create a next-mdx.json file at the root of your project with the following:

    {
      "post": {
        "contentPath": "content/posts",
        "basePath": "/blog",
        "sortBy": "date",
        "sortOrder": "desc"
      },
      "category": {
        "contentPath": "content/categories"
      }
    }
    1. post, category and author keys are unique IDs used as references for your MDX types.
    2. contentPath (required) is where your MDX files are located.
    3. basePath (optional) is the path used for generating URLs.
    4. sortBy (optional, defaults to title) is the name of the frontMatter field used for sorting.
    5. sortOrder (optional, defaults to asc) is the sorting order.

    Reference

    next-mdx exposes 6 main helper functions:

    • getMdxPaths(sourceName: string)
    • getNode(sourceName, context)
    • getAllNodes(sourceName)
    • getMdxNode(sourceName, context, params)
    • getAllMdxNodes(sourceName, params)
    • useHydrate(node, params)

    getMdxPaths

    getMdxPaths(sourceName: string) returns an array of path params which can be passed directly to paths in getStaticPaths`.

    • sourceName is the unique ID defined in next-mdx.json

    Example

    // pages/blog/[...slug].js
    import { getMdxPaths } from "next-mdx/server"
    
    export async function getStaticPaths() {
      return {
        paths: await getMdxPaths("post"),
        fallback: false,
      }
    }

    getNode

    getNode(sourceName, context) returns an MDXNode with frontMatter and relational data but without MDX data. This is really fast and cached.

    Use this instead of getMdxNode if you are not rendering MDX content on a page.

    • sourceName is the unique ID defined in next-mdx.json
    • context is the context passed to getStaticProps or the slug as a string.

    Example

    // pages/blog/[...slug].js
    import { getNode } from "next-mdx/server"
    
    export async function getStaticProps(context) {
      const post = await getNode("post", context)
    
      if (!post) {
        return {
          notFound: true,
        }
      }
    
      return {
        props: {
          post,
        },
      }
    }

    getAllNodes

    getAllNodes(sourceName) returns all MdxNode of the given type/source with frontMatter and relational data but without MDX data. This is also really fast and cached.

    • sourceName is the unique ID defined in next-mdx.json

    Example

    import { getAllNodes } from "next-mdx/server"
    
    export async function getStaticProps() {
      return {
        props: {
          posts: await getAllNodes("post"),
        },
      }
    }

    getMdxNode

    getMdxNode(sourceName, context, params) returns an MDXNode.

    • sourceName is the unique ID defined in next-mdx.json
    • context is the context passed to getStaticProps or the slug as a string.
    • params:
    {
      components?: MdxRemote.Components
      scope?: Record<string, unknown>
      provider?: MdxRemote.Provider
      mdxOptions?: {
        remarkPlugins?: Pluggable[]
        rehypePlugins?: Pluggable[]
        hastPlugins?: Pluggable[]
        compilers?: Compiler[]
        filepath?: string
      }
    }

    Example

    // pages/blog/[...slug].js
    import { getMdxNode } from "next-mdx/server"
    
    export async function getStaticProps(context) {
      const post = await getMdxNode("post", context)
    
      if (!post) {
        return {
          notFound: true,
        }
      }
    
      return {
        props: {
          post,
        },
      }
    }

    getAllMdxNodes

    getAllMdxNodes(sourceName, params) returns all MdxNode of the given type/source.

    • sourceName is the unique ID defined in next-mdx.json
    • params:
    {
      components?: { name: React.Component },
      scope?: {},
      provider?: { component: React.Component, props: Record<string, unknown> },
      mdxOptions: {
        remarkPlugins: [],
        rehypePlugins: [],
        hastPlugins: [],
        compilers: [],
      }
    }

    Example

    import { getAllMdxNodes } from "next-mdx/server"
    
    export async function getStaticProps() {
      const posts = await getAllMdxNodes("post")
    
      return {
        props: {
          posts: posts.filter((post) => post.frontMatter.featured),
        },
      }
    }

    useHydrate

    useHydrate(node, params) is used on the client side for hydrating static content.

    • node is the MdxNode object
    • params:
    {
      components?: { name: React.Component },
      provider?: { component: React.Component, props: Record<string, unknown> }
    }

    Example

    import { useHydrate } from "next-mdx/client"
    
    export default function PostPage({ post }) {
      const content = useHydrate(post)
    
      return (
        <div>
          <h1>{post.frontMatter.title}</h1>
    
          {content}
        </div>
      )
    }

    getAllNodes vs getAllMdxNodes

    Use getAllNodes when you need nodes without the MDX content. It is backed by a cache and is really fast. This is handy when you need a list of nodes (example post teasers) and you're not using the MDX content.

    MDX Components

    To use components inside MDX files, you need to pass the components to both getMdxNode/getAllMdxNodes and useHydrate.

    Example

    import { getMdxNode } from "next-mdx/server"
    import { useHydrate } from "next-mdx/client"
    
    export function Alert({ text }) {
      return <p>{text}</p>
    }
    
    export default function PostPage({ post }) {
      const content = useHydrate(post, {
        components: {
          Alert,
        },
      })
    
      return (
        <div>
          <h1>{post.frontMatter.title}</h1>
    
          {content}
        </div>
      )
    }
    
    export async function getStaticProps(context) {
      const post = await getMdxNode("post", context, {
        components: {
          Alert,
        },
      })
    
      return {
        props: {
          post,
        },
      }
    }

    MDX Options

    MDX options can be passed as params to both getMdxNode(sourceName, context, params) and getAllMdxNodes(sourceName, params) where params takes the shape of:

    export interface MdxParams {
      components?: MdxRemote.Components
      scope?: Record<string, unknown>
      provider?: MdxRemote.Provider
      mdxOptions?: {
        remarkPlugins?: Pluggable[]
        rehypePlugins?: Pluggable[]
        hastPlugins?: Pluggable[]
        compilers?: Compiler[]
        filepath?: string
      }
    }

    Relational Data

    When retrieving nodes with getMdxNode or getAllMdxNodes, next-mdx will automatically infer relational data from frontMatter keys.

    Convention

    1. The frontMatter field name must be the same as the key defined in next-mdx.json
    2. The frontMatter field must be an array of values.

    Example

    Given the following MDX files.

    .
    └── content
        ├── categories
        │   └── category-a.mdx
        │   └── category-b.mdx
        └── posts:
            └── example-post.mdx
    

    In example-post you can reference related categories using the following:

    ---
    title: Example Post
    category:
      - category-a
    ---

    You can then access the categories as follows:

    const post = getMdxNode("post", context)
    
    // post.relationships.category

    Plugins

    TypeScript

    Define your node types as follows:

    interface Post extends MdxNode<FrontMatterFields> {}

    Example

    import { MdxNode } from "next-mdx/server"
    
    interface Category
      extends MdxNode<{
        name: string
      }> {}
    
    interface Post
      extends MdxNode<{
        title: string
        excerpt?: string
        category?: string[]
      }> {
      relationships?: {
        category: Category[]
      }
    }

    You can then use Post as the return type for getNode, getAllNodes, getMdxNode and getAllMdxNode:

    const post = await getMdxNode<Post>("post", context)
    
    const posts = await getAllNodes<Post>("post")

    License

    Licensed under the MIT license.

    Keywords

    none

    Install

    npm i next-mdx

    DownloadsWeekly Downloads

    263

    Version

    0.6.2

    License

    MIT

    Unpacked Size

    47 kB

    Total Files

    28

    Last publish

    Collaborators

    • arshad