Prose UI with Next.js and Content Collections

Create a new project

Start by creating a new Next.js app and selecting the recommended Next.js defaults when prompted.

Create a new Next.js project
npx create-next-app@latest

Set up MDX rendering with Content Collections

Content Collections helps you organize your MDX content and render MDX files. The setup below follows the official Next.js quickstart and MDX guides.

You can also set up Prose UI with next/mdx. In this guide we use Content Collections because it keeps all Markdown files in a single place and provides a great developer experience.

Install the required packages:

npm install @content-collections/core @content-collections/next @content-collections/mdx zod -D

Add the content-collections path to your tsconfig.json:

tsconfig.json
{
  "compilerOptions": {
    // ...
    "paths": {
      "@/*": ["./*"],
      "content-collections": ["./.content-collections/generated"]
    }
  }
}

Update your Next.js configuration:

next.config.ts
import type { NextConfig } from "next";
import { withContentCollections } from "@content-collections/next";
 
const nextConfig: NextConfig = {};
 
export default withContentCollections(nextConfig);

Add .content-collections to your .gitignore:

.gitignore
.content-collections

Create a content-collections.ts file in the root of your project:

content-collections.ts
import { defineCollection, defineConfig } from "@content-collections/core";
import { compileMDX } from "@content-collections/mdx";
import { z } from "zod";
 
const posts = defineCollection({
  name: "posts",
  directory: "content",
  include: "**/*.mdx",
  schema: z.object({
    content: z.string(),
  }),
  transform: async (document, context) => {
    const mdx = await compileMDX(context, document);
    return {
      ...document,
      mdx,
    };
  },
});
 
export default defineConfig({
  collections: [posts],
});

Set up Prose UI and Markdown rendering

Install Prose UI dependencies:

npm install @prose-ui/core @prose-ui/next @prose-ui/style

Import the styles into your globals.css:

globals.css
@import "@prose-ui/style/prose-ui.css";
@import "@prose-ui/style/katex.min.css";

In content-collections.ts, initialize and pass remarkPlugins to the MDX compiler:

content-collections.ts
import { remarkPlugins } from '@prose-ui/core'

// ...
const content = await compileMDX(ctx, page, {
   remarkPlugins: remarkPlugins(),
})
// ...

Create the page that will render your Markdown at /app/posts/[[...path]]/page.tsx. Pass Prose UI mdxComponents to MDXContent, and wrap the content in an element with the prose-ui CSS class.

/app/posts/[[...path]]/page.tsx
import { notFound } from "next/navigation";
import { allPosts } from "content-collections";
import { mdxComponents } from "@prose-ui/next";
import { MDXContent } from "@content-collections/mdx/react";


type Params = Promise<{ path: string[] }>
type PageProps = {
  params: Params
}

const findPage = (pathArr: string[]) => {
  const path = pathArr ? `${pathArr.join('/')}` : '/'
  return allPosts.find((page) => page._meta.path === path)
}

export async function generateStaticParams() {
  return allPosts.map((page) => ({
    path: page._meta.path.slice(1).split('/'),
  }))
}

export default async function Page({
  params,
}: PageProps) {
  const { path } = await params
  let page = findPage(path)
  if (!page) notFound();

  return (
    <article className="prose-ui w-full max-w-5xl mx-auto">
      <MDXContent code={page.mdx} components={mdxComponents} />
    </article>
  )
}

Add your first page

Create a content/posts folder (if it doesn't exist yet) and add a page:

content/posts/hello.mdx
# Hello, world!

This stunning image is from [NASA's Juno mission](https://science.nasa.gov/mission/juno).

<Frame>
  <Image
    src="https://media.dhub.dev/jupiter-cropped.jpg"
    alt="Jupiter"
  />
  <Caption>Juno Captures Jupiter. Credits: NASA/JPL-Caltech/SwRI/MSSS, Thomas Thomopoulos © CC BY</Caption>
</Frame>

Run your project and open http://localhost:3000/posts/hello to view the rendered MDX page.

Created by Valdemaras, designed by Domas. Source code available on GitHub.