Prose UI with TanStack Start

This guide walks you through setting up a fresh TanStack Start project with Prose UI.

Start by creating a new project:

Create a new project
npm create @tanstack/start@latest

Set up MDX rendering with Content Collections

Content Collections helps you keep your MDX content organized and handles rendering your MDX files. The setup below is based on the Content Collections Next.js quickstart and the MDX guide.

Install the required packages:

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

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

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

Add the Content Collections Vite plugin to your Vite config. It must be the first plugin in the list:

vite.config.ts
import contentCollections from "@content-collections/vite";
// ...
 
export default defineConfig({
  plugins: [
    contentCollections()
    // ...
  ],
});

Add .content-collections to your .gitignore:

.gitignore
.content-collections

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

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 the Prose UI dependencies:

npm install @prose-ui/core @prose-ui/react @prose-ui/style sharp

Import the styles into your styles.css:

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

In your 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 route that will render your Markdown

Create the route at src/routes/posts/$.tsx. Pass Prose UI mdxComponents into MDXContent, and wrap the content in an element with the prose-ui CSS class:

src/routes/posts/$.tsx
import { createFileRoute, notFound } from '@tanstack/react-router'
import { allPosts } from 'content-collections'
import { mdxComponents } from '@prose-ui/react'
import { MDXContent } from '@content-collections/mdx/react'

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

export const Route = createFileRoute('/posts/$')({
  component: PostPage,
  loader: async ({ params }) => {
    const pathSegments = params._splat ? params._splat.split('/').filter(Boolean) : []
    const page = findPage(pathSegments)
    
    if (!page) {
      throw notFound()
    }
    
    return { page }
  },
})

function PostPage() {
  const { page } = Route.useLoaderData()
  
  if (!page) {
    throw notFound()
  }

  return (
    <div className="w-full bg-[hsl(var(--p-color-bg))] min-h-screen">
      <article className="prose-ui w-full max-w-3xl mx-auto px-4 py-8">
        <MDXContent code={page.mdx} components={mdxComponents} />
      </article>
    </div>
  )
}

Add your first page

Create a folder content/posts and add your first 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, then open http://localhost:3000/posts/hello to view your rendered MDX page.

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