Web-Design
Tuesday December 8, 2020 By David Quintanilla
How To Use MDX Stored In Sanity In A Next.js Website — Smashing Magazine


MDX offers you the minimalist ergonomics of Markdown with the flexibleness of customized elements. By combining MDX with Sanity and Subsequent, you possibly can construct strong, team-friendly content material modifying experiences whereas holding the nice and environment friendly developer expertise of constructing Jamstack websites with React.

Lately, my crew took on a challenge to construct a web-based, video-based studying platform. The challenge, referred to as Jamstack Explorers, is a Jamstack app powered by Sanity and Subsequent.js. We knew that the success of this challenge relied on making the modifying expertise simple for collaborators from totally different firms and roles, in addition to retaining the flexibleness so as to add customized elements as wanted.

Screenshot of Cassidy Williams’s Next.js mission on Jamstack Explorers.
The Jamstack Explorers website powered by Subsequent.js, Sanity, and MDX. (Large preview)

To perform this, we determined to writer content material utilizing MDX, which is Markdown with the choice to incorporate customized elements. For our viewers, Markdown is a normal method to writing content material: it’s how we format GitHub feedback, Notion docs, Slack messages (kinda), and lots of different instruments. The customized MDX elements are elective and their utilization is much like shortcodes in WordPress and templating languages.

To make it attainable to collaborate with contributors from anyplace, we determined to make use of Sanity as our content material administration system (CMS).

However how may we write MDX in Sanity? On this tutorial, we’ll break down how we arrange MDX assist in Sanity, and the best way to load and render that MDX in Next.js — powered web site utilizing a lowered instance.

TL;DR

If you wish to soar straight to the outcomes, listed here are some useful hyperlinks:

How To Write Content material Utilizing MDX In Sanity

Our first step is to get our content material administration workflow arrange. On this part, we’ll stroll via organising a brand new Sanity occasion, including assist for writing MDX, and making a public, read-only API that we will use to load our content material into an internet site for show.

Create A New Sanity Occasion

For those who don’t have already got a Sanity occasion arrange, let’s begin with that. For those who do have already got a Sanity occasion, skip forward to the following part.

Our first step is to install the Sanity CLI globally, which permits us to put in, configure, and run Sanity regionally.

# set up the Sanity CLI
npm i -g @sanity/cli

In your challenge folder, create a brand new listing referred to as sanity, transfer into it, and run Sanity’s init command to create a brand new challenge.

# create a brand new listing to comprise Sanity recordsdata
mkdir sanity
cd sanity/
sanity init

The init command will ask a sequence of questions. You may select no matter is sensible in your challenge, however on this instance we’ll use the next choices:

  • Select a challenge identify: Sanity Subsequent MDX Instance.
  • Select the default dataset configuration (“manufacturing”).
  • Use the default challenge output path (the present listing).
  • Select “clear challenge” from the template choices.

Set up The Markdown Plugin For Sanity

By default, Sanity doesn’t have Markdown assist. Thankfully, there’s a ready-made Sanity plugin for Markdown support that we will set up and configure with a single command:

# add the Markdown plugin
sanity set up markdown

This command will set up the plugin and add the suitable configuration to your Sanity occasion to make it accessible to be used.

Outline A Customized Schema With A Markdown Enter

In Sanity, we management each content material sort and enter utilizing schemas. That is one in all my favourite options about Sanity, as a result of it signifies that I’ve fine-grained management over what every content material sort shops, how that content material is processed, and even how the content material preview is constructed.

For this instance, we’re going to create a easy web page construction with a title, a slug for use within the web page URL, and a content material space that expects Markdown.

Create this schema by including a brand new file at sanity/schemas/web page.js and including the next code:

export default {
  identify: 'web page',
  title: 'Web page',
  sort: 'doc',
  fields: [
    {
      name: 'title',
      title: 'Page Title',
      type: 'string',
      validation: (Rule) => Rule.required(),
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      validation: (Rule) => Rule.required(),
      options: {
        source: 'title',
        maxLength: 96,
      },
    },
    {
      name: 'content',
      title: 'Content',
      type: 'markdown',
    },
  ],
};

We begin by giving the entire content material sort a reputation and title. The kind of doc tells Sanity that this ought to be displayed on the high stage of the Sanity Studio as a content material sort somebody can create.

Every discipline additionally wants a reputation, title, and sort. We are able to optionally present validation rules and different choices, equivalent to giving the slug a max size and permitting it to be generated from the title worth.

Add A Customized Schema To Sanity’s Configuration

After our schema is outlined, we have to inform Sanity to make use of it. We do that by importing the schema into sanity/schemas/schema.js, then including it to the sorts array handed to createSchema.


  // First, we should import the schema creator
  import createSchema from 'half:@sanity/base/schema-creator';

  // Then import schema sorts from any plugins which may expose them
  import schemaTypes from 'all:half:@sanity/base/schema-type';

+ // Import customized schema sorts right here
+ import web page from './web page';

  // Then we give our schema to the builder and supply the outcome to Sanity
  export default createSchema({
    // We identify our schema
    identify: 'default',
    // Then proceed to concatenate our doc sort
    // to those supplied by any plugins which are put in
    sorts: schemaTypes.concat([
-     /* Your types here! */
+     page,
    ]),
  });

This places our web page schema into Sanity’s startup configuration, which implies we’ll be capable to create pages as soon as we begin Sanity up!

Run Sanity Studio Regionally

Now that now we have a schema outlined and configured, we will begin Sanity regionally.

sanity begin

As soon as it’s operating, we will open Sanity Studio at http://localhost:3333 on our native machine.

After we go to that URL, we’ll must log within the first time. Use your most well-liked account (e.g. GitHub) to authenticate. When you get logged in, you’ll see the Studio dashboard, which appears to be like fairly barebones.

Sanity Studio home page showing the Page type.
The Sanity Studio residence web page. (Large preview)

So as to add a brand new web page, click on “Web page”, then the pencil icon on the top-left.

Add a title and slug, then write some Markdown with MDX within the content material space:

That is written in [Markdown](https://www.markdownguide.org/basic-syntax/).

However what’s this?

<Callout>

Oh dang! Is that this a React part in the course of our content material? 😱

</Callout>

Holy buckets! That’s wonderful!

Heads up! The empty line between the MDX part and the Markdown it accommodates is required. In any other case the Markdown gained’t be parsed. This will probably be fixed in MDX v2.

Page editing view in Sanity Studio with values filled in
Enhancing a web page in Sanity Studio. (Large preview)

Upon getting the content material in place, click on “Publish” to make it accessible.

Deploy The Sanity Studio To A Manufacturing URL

To be able to make edits to the positioning’s information with out having to run the code regionally, we have to deploy the Sanity Studio. The Sanity CLI makes this attainable with a single command:

sanity deploy

Select a hostname for the positioning, which will probably be used within the URL. After that, will probably be deployed and reachable at your personal customized hyperlink.

Terminal output after deploying Sanity Studio using the Sanity CLI.
Terminal output after deploying Sanity Studio utilizing the CLI. (Large preview)

This supplies a manufacturing URL for content material editors to log in and make modifications to the positioning content material.

Make Sanity Content material Obtainable Through GraphQL

Sanity ships with assist for GraphQL, which we’ll use to load our web page information into our website’s front-end. To allow this, we have to deploy a GraphQL API, which is one other one-liner:

sanity graphql deploy

We are able to select to allow a GraphQL Playground, which supplies us a browser-based information explorer. That is extraordinarily useful for testing queries.

Terminal output after deploying the Sanity GraphQL API using the CLI.
Terminal output after deploying the GraphQL API utilizing Sanity’s CLI. (Large preview)

Retailer the GraphQL URL — you’ll want it to load the info into Subsequent.js!

https://sqqecrvt.api.sanity.io/v1/graphql/manufacturing/default

The GraphQL API is read-only for printed content material by default, so we don’t want to fret about holding this secret — all the pieces that this API returns is printed, which implies it’s what we wish individuals to see.

Take a look at Sanity GraphQL Queries In The Browser

By opening the URL of our GraphQL API, we’re in a position to take a look at out GraphQL queries to verify we’re getting the info we anticipate. These queries are copy-pasteable into our code.

To load our web page information, we will construct the next question utilizing the “schema” tab on the right-hand aspect as a reference.

question AllPages {
  allPage {
    title
    slug {
      present
    }
    content material
  }
}

This question hundreds all of the pages printed in Sanity, returning the title, present slug, and content material for every. If we run this within the playground by urgent the play button, we will see our web page returned.

A GraphQL query and result displayed in the GraphQL Playground.
Sanity comes with a built-in GraphQL Playground for testing queries within the browser. (Large preview)

Now that we’ve acquired web page information with MDX in it getting back from Sanity, we’re able to construct a website utilizing it!

Within the subsequent part, we’ll create an Subsequent.js website that hundreds information from Sanity and renders our MDX content material correctly.

Show MDX In Subsequent.js From Sanity

In an empty listing, begin by initializing a brand new package deal.json, then set up Subsequent, React, and a package deal referred to as next-mdx-remote.

# create a brand new package deal.json with the default choices
npm init -y

# set up the packages we want for this challenge
npm i subsequent react react-dom next-mdx-remote

Inside package deal.json, add a script to run subsequent dev:

  {
    "identify": "sanity-next-mdx",
    "model": "1.0.0",
    "scripts": {
+     "dev": "subsequent dev"
    },
    "writer": "Jason Lengstorf <jason@lengstorf.com>",
    "license": "ISC",
    "dependencies": {
      "subsequent": "^10.0.2",
      "next-mdx-remote": "^1.0.0",
      "react": "^17.0.1",
      "react-dom": "^17.0.1"
    }

Create React Elements To Use In MDX Content material

In our web page content material, we used the <Callout> part to wrap a few of our Markdown. MDX works by combining React elements with Markdown, which implies our first step is to outline the React part our MDX expects.

Create a Callout part at src/elements/callout.js:

export default perform Callout({ youngsters }) {
  return (
    <div
      fashion={{
        padding: '0 1rem',
        background: 'lightblue',
        border: '1px stable blue',
        borderRadius: '0.5rem',
      }}
    >
      {youngsters}
    </div>
  );
}

This part provides a blue field round content material that we wish to name out for additional consideration.

Ship GraphQL Queries Utilizing The Fetch API

It is probably not apparent, however you don’t want a particular library to ship GraphQL queries! It’s attainable to ship a question to a GraphQL API utilizing the browser’s built-in Fetch API.

Since we’ll be sending just a few GraphQL queries in our website, let’s add a utility perform that handles this so we don’t need to duplicate this code in a bunch of locations.

Add a utility perform to fetch Sanity information utilizing the Fetch API at src/utils/sanity.js:

export async perform getSanityContent({ question, variables = {} }) {
  const { information } = await fetch(
    'https://sqqecrvt.api.sanity.io/v1/graphql/manufacturing/default',
    {
      technique: 'POST',
      headers: {
        'Content material-Kind': 'utility/json',
      },
      physique: JSON.stringify({
        question,
        variables,
      }),
    },
  ).then((response) => response.json());

  return information;
}

The primary argument is the Sanity GraphQL URL that Sanity returned after we deployed the GraphQL API.

GraphQL queries are all the time despatched utilizing the POST technique and the utility/json content material sort header.

The physique of a GraphQL request is a stringified JSON object with two properties: question, which accommodates the question we wish to execute as a string; and variables, which is an object containing any question variables we wish to cross into the GraphQL question.

The response will probably be JSON, so we have to deal with that within the .then for the question outcome, after which we will destructure the outcome to get to the info inside. In a manufacturing app, we’d wish to examine for errors within the outcome as nicely and show these errors in a useful approach, however this can be a submit about MDX, not GraphQL, so #yolo.

Heads up! The Fetch API is nice for easy use circumstances, however as your app turns into extra complicated you’ll most likely wish to look into the advantages of utilizing a GraphQL-specific device like Apollo or urql.

Create A Itemizing Of All Pages From Sanity In Subsequent.js

To start out, let’s make an inventory of all of the pages printed in Sanity, in addition to a hyperlink to their slug (which gained’t work simply but).

Create a brand new file at src/pages/index.js and put the next code inside:

import Hyperlink from 'subsequent/hyperlink';
import { getSanityContent } from '../utils/sanity';

export default perform Index({ pages }) {
  return (
    <div>
      <h1>This Web site Hundreds MDX From Sanity.io</h1>
      <p>View any of those pages to see it in motion:</p>
      <ul>
        {pages.map(({ title, slug }) => (
          <li key={slug}>
            <Hyperlink href={`/${slug}`}>
              <a>{title}</a>
            </Hyperlink>
          </li>
        ))}
      </ul>
    </div>
  );
}

export async perform getStaticProps() {
  const information = await getSanityContent({
    question: `
      question AllPages {
        allPage {
          title
          slug {
            present
          }
        }
      }
    `,
  });

  const pages = information.allPage.map((web page) => ({
    title: web page.title,
    slug: web page.slug.present,
  }));

  return {
    props: { pages },
  };
}

In getStaticProps we name the getSanityContent utility with a question that hundreds the title and slug of all pages in Sanity. We then map over the web page information to create a simplified object with a title and slug property for every web page and return that array as a pages prop.

The Index part to show this web page receives that web page’s prop, so we map over that to output an unordered record of hyperlinks to the pages.

Begin the positioning with npm run dev and open http://localhost:3000 to see the work in progress.

The site loaded in localhost with a list of linked Sanity page titles.
Utilizing getStaticProps means this web page may also work with out JavaScript enabled! (Large preview)

If we click on a web page hyperlink proper now, we’ll get a 404 error. Within the subsequent part we’ll repair that!

Generate Pages Programatically In Subsequent.js From CMS Knowledge

Subsequent.js helps dynamic routes, so let’s arrange a brand new file to catch all pages besides our residence web page at src/pages/[page].js.

On this file, we have to inform Subsequent what the slugs are that it must generate utilizing the getStaticPaths perform.

To load the static content material for these pages, we have to use getStaticProps, which can obtain the present web page slug in params.web page.

To assist visualize what’s taking place, we’ll cross the slug via to our web page and log the props out on display screen for now.

import { getSanityContent } from '../utils/sanity';

export default perform Web page(props) {
  return <pre>{JSON.stringify(props, null, 2)}</pre>;
}

export async perform getStaticProps({ params }) {
  return {
    props: {
      slug: params.web page,
    },
  };
}

export async perform getStaticPaths() {
  const information = await getSanityContent({
    question: `
      question AllPages {
        allPage {
          slug {
            present
          }
        }
      }
    `,
  });

  const pages = information.allPage;

  return {
    paths: pages.map((p) => `/${p.slug.present}`),
    fallback: false,
  };
}

If the server is already operating this may reload routinely. If not, run npm run dev and click on one of many web page hyperlinks on http://localhost:3000 to see the dynamic route in motion.

A JSON object displayed in the browser containing the current page’s slug.
(Large preview)

Load Web page Knowledge From Sanity For The Present Web page Slug In Subsequent.js

Now that now we have the web page slug, we will ship a request to Sanity to load the content material for that web page.

Utilizing the getSanityContent utility perform, ship a question that hundreds the present web page utilizing its slug, then pull out simply the web page’s information and return that within the props.

  export async perform getStaticProps({ params }) {
+   const information = await getSanityContent({
+     question: `
+       question PageBySlug($slug: String!) {
+         allPage(the place: { slug: { present: { eq: $slug } } }) {
+           title
+           content material
+         }
+       }
+     `,
+     variables: {
+       slug: params.web page,
+     },
+   });
+
+   const [pageData] = information.allPage;

    return {
      props: {
-       slug: params.web page,
+       pageData,
      },
    };
  }

After reloading the web page, we will see that the MDX content material is loaded, however it hasn’t been processed but.

A JSON object displayed in the browser with the title and unprocessed MDX content for the page.
We’ve the content material now, however we want a bit extra processing earlier than it’s going to show correctly. (Large preview)

Render MDX From A CMS In Subsequent.js With Subsequent-mdx-remote

To render the MDX, we have to carry out two steps:

  1. For the build-time processing of MDX, we have to render the MDX to a string. This may flip the Markdown into HTML and make sure that the React elements are executable. That is completed by passing the content material as a string into renderToString together with an object containing the React elements we wish to be accessible in MDX content material.

  2. For the client-side rendering of MDX, we hydrate the MDX by passing within the rendered string and the React elements. This makes the elements accessible to the browser and unlocks interactivity and React options.

Whereas this would possibly really feel like doing the work twice, these are two distinct processes that permit us to each create totally rendered HTML markup that works with out JavaScript enabled and the dynamic, client-side performance that JavaScript supplies.

Make the next modifications to src/pages/[page].js to render and hydrate MDX:

+ import hydrate from 'next-mdx-remote/hydrate';
+ import renderToString from 'next-mdx-remote/render-to-string';
  import { getSanityContent } from '../utils/sanity';
+ import Callout from '../elements/callout';

- export default perform Web page(props) {
-   return <pre>{JSON.stringify(props, null, 2)}</pre>;
+ export default perform Web page({ title, content material }) {
+   const renderedContent = hydrate(content material, {
+     elements: {
+       Callout,
+     },
+   });
+
+   return (
+     <div>
+       <h1>{title}</h1>
+       {renderedContent}
+     </div>
+   );
  }

  export async perform getStaticProps({ params }) {
    const information = await getSanityContent({
      question: `
          question PageBySlug($slug: String!) {
            allPage(the place: { slug: { present: { eq: $slug } } }) {
              title
              content material
            }
          }
        `,
      variables: {
        slug: params.web page,
      },
    });

    const [pageData] = information.allPage;

+   const content material = await renderToString(pageData.content material, {
+     elements: { Callout },
+   });

    return {
      props: {
-       pageData,
+       title: pageData.title,
+       content material,
      },
    };
  }

  export async perform getStaticPaths() {
    const information = await getSanityContent({
      question: `
          question AllPages {
            allPage {
              slug {
                present
              }
            }
          }
        `,
    });

    const pages = information.allPage;

    return {
      paths: pages.map((p) => `/${p.slug.present}`),
      fallback: false,
    };
  }

After saving these modifications, reload the browser and we will see the web page content material being rendered correctly, customized React elements and all!

The MDX content rendered properly in the browser.
The customized part will present up with and with out JavaScript enabled! (Large preview)

Use MDX With Sanity And Subsequent.js For Versatile Content material Workflows

Now that this code is about up, content material editors can rapidly write content material utilizing MDX to allow the pace of Markdown with the flexibleness of customized React elements, all from Sanity! The location is about as much as generate all of the pages printed in Sanity, so except we wish to add new customized elements we don’t want to the touch the Subsequent.js code in any respect to publish new pages.

What I like about this workflow is that it lets me hold my favourite components of a number of instruments: I actually like writing content material in Markdown, however my content material additionally wants extra flexibility than the usual Markdown syntax supplies; I like constructing web sites with React, however I don’t like managing content material in Git.

Past this, I even have entry to the large quantity of customization made accessible in each the Sanity and React ecosystems, which looks like having my cake and consuming it, too.

For those who’re in search of a brand new content material administration workflow, I hope you get pleasure from this one as a lot as I do!

What’s Subsequent?

Now that you just’ve acquired a Subsequent website utilizing MDX from Sanity, you could wish to go additional with these tutorials and sources:

What’s going to you construct with this workflow? Let me know on Twitter!

Smashing Editorial
(yk)





Source link