Building Null MDX: the first v0 Template with real MDX support

Introduction

When Vercel shipped the new v0 in early 2026, they didn’t just polish the interface, they rebuilt the entire runtime. The new v0 runs sandbox-based Linux VMs with full Node.js environments. For most people, that meant better code generation. For me, it meant a question: can we finally build a documentation template that compiles real markdown inside v0?

The answer is Null MDX.

The Problem with Docs Templates

Every documentation template I tried in v0 had the same limitation: no real MDX support. Content was hardcoded in JSX, stored as JSON, or rendered with basic markdown parsers that couldn’t handle components, syntax highlighting, or the plugin ecosystem that makes MDX powerful.

The reason was simple: the old v0 runtime couldn’t run compilers. MDX compilation requires next-mdx-remote (or a similar compiler), Shiki or Prism for syntax highlighting, and remark/rehype plugins for processing. These are Node.js packages that need a real runtime to execute.

How the New v0 VM Makes It Possible

The new v0 is fundamentally different. As Vercel described in their announcement, the new sandbox-based runtime can “import any GitHub repo and automatically pull environment variables and configurations.” Every prompt generates production-ready code in a real environment.

This means v0’s sandbox runs an actual Node.js process. It installs dependencies. It executes build tools. It runs compilers. And critically for Null MDX, it compiles .mdx files server-side using the full next-mdx-remote pipeline:

  1. MDX Compilation: next-mdx-remote reads .mdx files from the content directory and compiles them to React components at request time
  2. Syntax Highlighting: Shiki processes code blocks with full language grammars, producing accurate, theme-aware highlighted code
  3. Plugin Pipeline: remark-gfm enables GitHub Flavored Markdown (tables, strikethrough, autolinks), rehype-pretty-code handles code block formatting, and rehype-slug/rehype-autolink-headings generate navigable anchor links
  4. Custom Components: MDX components like Callout, Steps, and Tabs are mapped to React components and rendered server-side

All of this runs live inside v0. You can open the template, edit an .mdx file, and see compiled, syntax-highlighted, component-rich documentation render in the preview pane.

What Null MDX Includes

Null MDX is a production-ready Next.js 16 template designed for content-heavy sites. Here’s what ships out of the box:

  • Dual-Mode Architecture: Toggle between docs mode (sidebar navigation, ordered sections) and blog mode (chronological posts, author attribution, tags) with a single config change in lib/site-config.tsx.

  • Full-Text Search: A built-in search system with keyboard shortcut support (Cmd+K) that indexes all content at build time.

  • Table of Contents: Auto-generated from headings with real-time scroll tracking, so readers always know where they are in long documents.

  • Reading Progress: A visual progress indicator for long-form content.

  • RSS Feed: Auto-generated at /blog/feed.xml for syndication.

  • LLMs.txt: An AI-friendly content index at /docs/llms.txt, making your documentation discoverable by AI agents and language models.

  • Design System Viewer: A built-in design system page at /design that showcases all available components.

Optional Supabase Asset Management (Inspired by Lee Robinson at Cursor)

One of the features I’m most proud of is the optional Supabase Storage integration. The inspiration came directly from Lee Robinson’s blog post about migrating cursor.com off its CMS (Coding Agents & Complexity Budgets | Lee Robinson).

In that post, Lee describes how Cursor was spending $56,848 on CMS CDN usage after just a few months. The fix was straightforward: host assets in object storage and build a simple GUI for managing them. As Lee put it: “This took only 3 or 4 prompts with the agent to get something decent and workable.”

Null MDX ships with exactly that pattern, pre-built. Here’s what the Supabase integration provides:

  1. Asset API (/api/null-mdx/assets): A full CRUD API for Supabase Storage. Upload files via multipart form data, list assets sorted by creation date, delete by path. Timestamped filenames prevent collisions. Files are cached with Cache-Control: max-age=31536000 (1 year, immutable).

  2. Individual Asset Serving (/api/null-mdx/assets/[name]): A proxy route that downloads files from Supabase Storage and serves them with proper MIME types. Supports images (JPEG, PNG, GIF, WebP, SVG), PDFs, and video (MP4, WebM). Responses are marked immutable for aggressive CDN caching.

  3. Optimized Image Rendering: The getOptimizedImageUrl utility generates Supabase’s image transformation URLs with configurable width and quality parameters. Blog cards and MDX image components use this automatically - meaning every image in your content is optimized without manual intervention.

  4. Asset Manager UI (/assets): A full drag-and-drop upload interface with grid view, one-click URL copying, and deletion. This is the “simple GUI on top of object storage” that Lee described building for Cursor.

  5. Graceful Fallback: The entire Supabase layer is optional. If NEXT_PUBLIC_SUPABASE_URL is not set, all image helpers fall back to serving from the local /images/ directory. The template works identically either way - Supabase just gives you cloud storage and optimization when you need it.

The key insight from Lee’s post applies perfectly here: the cost of the CMS abstraction is rarely worth it for developer-focused content sites. Your content is already code (MDX files in a git repo). Your assets should be in simple, cheap object storage. And the GUI for managing them should be something you can build in a few prompts - or in Null MDX’s case, something that ships out of the box.

Multi-Zone Architecture with Null Proxy

Null MDX is designed to be one half of a complete system. The companion template, Null Proxy, serves as a Multi-Zone gateway that routes requests from a single domain to multiple Next.js applications.

Here’s the architecture:

User Browser
    |
    v
Null Proxy (Landing Page + Router)
    |
    |-- /           -> Local landing page
    |-- /docs/*     -> Null MDX (Documentation)
    |-- /blog/*     -> Null MDX (Blog)
    |-- /about      -> Null MDX (About)
    v
Null MDX (Content Engine)

This means you deploy Null MDX as your content engine, deploy Null Proxy as your public-facing gateway, and users see a single, unified domain. The proxy handles all routing transparently via Next.js rewrites, and static assets are served correctly through the assetPrefix configuration.

Why two repositories? Separation of concerns. Your marketing landing page and your documentation content can be owned by different teams, deployed independently, and scaled separately. Update your docs without touching the landing page. Redesign the landing page without rebuilding the docs.

The “Null” Philosophy

The name reflects the design philosophy: start from nothing, with zero assumptions. No opinionated styling to fight against. No bloated features to rip out. Just a clean, minimal foundation that scales with your needs.

Every piece of Null MDX is intentional. The styling uses Tailwind CSS v4 and shadcn/ui, so you get a modern component library without lock-in. The content structure uses standard .mdx files in a content/ directory, so there’s no proprietary format. The configuration is a single TypeScript file, so there’s no hidden complexity.

Getting Started

You can fork either template directly from v0:

Or clone from GitHub:

Live demos:

Both templates are MIT licensed and built for the v0 community.

What’s Next

This is just the beginning of the Null ecosystem. The combination of v0’s new VM capabilities and Next.js Multi-Zones opens up patterns that weren’t possible before. Imagine a suite of templates - each responsible for a different concern - all composable under a single domain through Null Proxy.

Documentation, blog, changelog, API reference, marketing site. Each built independently. Each deployable independently. All unified through one routing layer.

Start from nothing. Build everything.


3 Likes