npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fumadocs-mdx-cloudflare

v0.1.0

Published

Remote MDX rendering on Cloudflare Workers for fumadocs

Readme

fumadocs-mdx-cloudflare

Remote MDX rendering on Cloudflare Workers for fumadocs.

Cloudflare Workers don't allow new Function() or eval(), which breaks the standard fumadocs MDX rendering pipeline. This package works around that by compiling MDX into an ES module that workerd parses natively, then executes it as a Dynamic Worker via the WorkerLoader binding.

Install

bun add fumadocs-mdx-cloudflare

Peer dependencies:

bun add @fumadocs/mdx-remote @cloudflare/worker-bundler react react-dom

For the source adapter, also install:

bun add fumadocs-core

Setup

Add a WorkerLoader binding to your wrangler.jsonc:

{
  "worker_loader": [
    { "binding": "RENDERER" }
  ]
}

If using the cache wrapper, add a KV namespace:

{
  "kv_namespaces": [
    { "binding": "CACHE", "id": "your-kv-namespace-id" }
  ]
}

Usage

Basic rendering

import { createRenderer } from "fumadocs-mdx-cloudflare";

const renderer = createRenderer({
  workerLoader: env.RENDERER,
  compilerOptions: {
    rehypeCodeOptions: {
      themes: { light: "github-light", dark: "github-dark" },
    },
  },
});

const { html, toc } = await renderer.render(mdxSource);
// html: string — rendered HTML
// toc: TocItem[] — { title: string, url: string, depth: number }

With caching

withCache wraps a renderer with KV read-through caching. It checks KV first, renders on cache miss, and stores the result.

import { createRenderer, withCache } from "fumadocs-mdx-cloudflare";

const renderer = createRenderer({
  workerLoader: env.RENDERER,
});

const cached = withCache(renderer, {
  kv: env.CACHE,
  prefix: "blog",
});

// Checks KV for blog:html:{slug} and blog:toc:{slug}
// On miss: renders, stores, returns
const { html, toc } = await cached.render("my-post", mdxSource);

// Invalidate when content changes
await cached.invalidate("my-post");

Use renderer.render(source) for uncached renders (e.g. draft previews) and cached.render(key, source) for production reads.

Configuration

const renderer = createRenderer({
  // Required: WorkerLoader binding from wrangler.jsonc
  workerLoader: env.RENDERER,

  // Passed to @fumadocs/mdx-remote createCompiler
  compilerOptions: {
    rehypeCodeOptions: { themes: { light: "github-light", dark: "github-dark" } },
  },

  // Pinned React version for the worker bundle (fetched from npm at runtime)
  react: { version: "19.2.4" },

  // Prefix for worker loader IDs — avoids collisions with multiple renderers
  namespace: "blog",

  // Dynamic worker compatibility settings
  worker: {
    compatibilityDate: "2026-01-01",
    compatibilityFlags: ["nodejs_compat"],
    globalOutbound: null, // sandbox: no network access from renderer
  },

  // Forwarded to @cloudflare/worker-bundler createWorker
  bundler: {
    minify: false,
    sourcemap: false,
  },
});

Source adapter

createR2Source maps MDX files in an R2 bucket to a fumadocs Source, which can be passed to the fumadocs loader() for page trees, URL generation, and querying.

import { createR2Source } from "fumadocs-mdx-cloudflare/source";
import { z } from "zod";

const frontmatterSchema = z.object({
  title: z.string(),
  description: z.string().optional(),
  publishedAt: z.string(),
});

const source = await createR2Source({
  bucket: env.CONTENT,
  prefix: "blog/",
  schema: frontmatterSchema,
  slug: (key) => key.replace(/^blog\//, "").replace(/\.mdx?$/, ""),
});

// source is a fumadocs Source — use with loader()

The schema option accepts a Zod schema (anything with .parse()) or a plain function (data: unknown) => T. Objects that fail validation are silently skipped.

How it works

fumadocs compiles MDX with outputFormat: 'function-body', which produces code like:

"use strict";
const { Fragment, jsxDEV } = arguments[0];
function MDXContent(props) { /* ... */ }
return { default: MDXContent, toc };

Normally you'd evaluate this with new Function() — but Cloudflare Workers blocks dynamic code evaluation.

This package instead embeds the compiled body inside a regular function in a static ES module:

import * as jsxRuntime from "react/jsx-runtime";
import { renderToString } from "react-dom/server.browser";

function evaluateCompiled(_jsxRuntime) {
  // compiled MDX body pasted here
  // arguments[0] resolves to _jsxRuntime
}

const { default: Content, toc } = evaluateCompiled(jsxRuntime);

export default {
  async fetch() {
    return new Response(JSON.stringify({
      html: renderToString(Content({})),
      toc,
    }));
  },
};

This module is bundled with React via @cloudflare/worker-bundler and loaded as a Dynamic Worker through the WorkerLoader binding. The compiled body is part of the module source at parse time — no eval() or new Function() at runtime.

Content-addressed hashing (SHA-256) of the compiled body deduplicates workers: identical MDX produces the same worker ID, so repeated renders reuse the existing worker.

API

fumadocs-mdx-cloudflare

| Export | Description | |---|---| | createRenderer(options) | Create a renderer that compiles and executes MDX on a Dynamic Worker | | withCache(renderer, options) | Wrap a renderer with KV read-through caching | | buildRendererModule(compiled) | Build the ES module source from compiled MDX (advanced) |

fumadocs-mdx-cloudflare/source

| Export | Description | |---|---| | createR2Source(options) | Create a fumadocs Source from R2 bucket content |

License

MIT