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

hono-decks

v0.5.0

Published

Slide decks for Hono and Cloudflare Workers

Readme

hono-decks

English | 日本語

hono-decks serves MDX slide decks from Hono applications and Cloudflare Workers. It compiles MDX into TypeScript modules at build time, so the Worker runtime does not load the filesystem or the compiler.

Quick start

The examples below use Bun. npm, pnpm, and Yarn are supported as well.

bun add hono hono-decks
bunx hono-decks init
bunx hono-decks compile

If you use an AI agent, run npx @tanstack/intent@latest install. The published package includes a versioned hono-decks skill that the agent can discover from node_modules.

init creates two files without overwriting existing files:

  • hono-decks.config.ts: the shared configuration for the CLI and runtime
  • src/decks.ts: an editable facade that connects the generated modules to your application
// hono-decks.config.ts
import { defineDecksConfig } from "hono-decks";

export default defineDecksConfig({
  mountPath: "/decks",
  build: {
    root: "decks",
    outDir: "src/generated",
  },
});
// src/decks.ts
import config from "../hono-decks.config";
import { createDecks } from "./generated/decks";

export const decks = createDecks(config);
// src/index.ts
import { Hono } from "hono";
import { decks } from "./decks";

const app = new Hono();
app.get("/", (c) => c.redirect(decks.paths("welcome").viewer));
app.route(decks.mountPath, decks.router());
export default app;

Create decks/welcome/deck.mdx, then run bunx hono-decks compile. The command updates src/generated/decks.ts and the generated slide modules. Do not edit the generated directory directly.

One config, one runtime kit

You do not need to repeat the mount path across compiler options, app.route(), and asset URLs. Define mountPath once in the config. The generated createDecks(config) function returns a configured kit:

  • decks.mountPath: the normalized path to pass to app.route()
  • decks.source: the DeckSource after applying the config's source() decorator
  • decks.router(overrides?): a router that safely deep-merges config and call-site overrides
  • decks.context(overrides?): middleware for application-owned routes
  • decks.paths(slug): the complete route map for the viewer, renderer, print view, presentation views, embeds, exports, OGP image, and assets
const paths = decks.paths("product");
paths.viewer;       // /decks/product
paths.render;       // /decks/product/render
paths.print;        // /decks/product/print
paths.presentation; // /decks/product/presentation
paths.presenter;    // /decks/product/presenter
paths.embed;        // /decks/product/embed
paths.exportPdf;    // /decks/product/export.pdf
paths.exportPng;    // /decks/product/export.png
paths.ogImage;      // /decks/product/og.png
paths.assets;       // /decks/product/assets

Viewer callbacks receive the same map as input.meta.paths. Use it instead of rebuilding routes with string concatenation.

export default defineDecksConfig({
  mountPath: "/decks",
  router: {
    viewer: {
      controls: {
        after: ({ meta }) => [
          {
            type: "link",
            href: meta.paths.viewer + "/about",
            label: "Details",
          },
        ],
      },
    },
  },
});

Development integration

For Cloudflare Workers, register the deck compiler as a Wrangler custom build.

// wrangler.jsonc
{
  "build": {
    "command": "hono-decks compile",
    "watch_dir": ["decks"]
  }
}

wrangler dev --live-reload performs the initial compile, watches MDX, deck-local components, assets, and theme CSS, and reloads the browser. Wrangler runs the same build command during deployment.

For HonoX or another Vite application, add the plugin to the existing Vite config.

import { honoDecks } from "hono-decks/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [honoDecks()],
});

The plugin compiles before Vite starts and adds the deck root and hono-decks.config.ts to the Vite watcher. After a successful recompile, it requests a full browser reload. In both setups, the application's normal dev command is enough for authoring.

Runtime configuration

Every resolver receives one object argument. Callbacks remain readable as inputs are added because their meaning does not depend on positional parameters.

interface AppEnv {
  Bindings: {
    BROWSER?: DeckBrowserRunBinding;
    DECK_EXPORT_TOKEN?: string;
  };
}

export default defineDecksConfig<AppEnv>({
  mountPath: "/decks",
  build: { root: "decks", outDir: "src/generated" },
  router: {
    presenter: {
      enabled: ({ dev }) => dev,
      viewerControl: true,
    },
    export: {
      authorize: ({ c }) =>
        c.req.header("authorization") === "Bearer " + c.env.DECK_EXPORT_TOKEN,
      browser: ({ c }) => c.env.BROWSER,
      pdf: true,
      png: true,
    },
  },
});

When dev is omitted, hono-decks uses the NODE_ENV value supplied by Vite or Wrangler. The standard Vite and wrangler dev commands are treated as development, while production builds and wrangler deploy are treated as production. An explicit dev: false value or resolver takes precedence. Unknown environments fail closed as production.

Use decks.router({ viewer: ... }) for call-site overrides. Nested options for viewer controls, document surfaces, the presenter, embeds, and exports are merged without discarding the config. Disable a feature explicitly with values such as presenter: false, embed: false, or export: false.

Custom application routes

import type { DeckContextVariables } from "hono-decks";
import { decks } from "./decks";

const app = new Hono<{ Variables: DeckContextVariables }>();

app.get(
  decks.mountPath + "/:slug/about",
  decks.context(),
  (c) => c.json({
    title: c.var.deckMeta.title,
    viewer: c.var.deckMeta.paths.viewer,
    slides: c.var.deckToc,
  }),
);

decks.context() shares the configured source, mount path, development policy, and viewer controls. Application routes and the built-in viewer therefore use the same draft policy and URLs.

To replace a built-in page, configure viewer.render. The callback receives frame, controls, toc, and meta.paths, allowing an application-owned layout to reuse the standard parts.

Deck-local components and CSS

A directory deck can use this structure:

decks/product/
  deck.mdx
  theme.css
  assets/
    diagram.svg
  components/
    index.tsx
    client/
      index.tsx

components/index.tsx contains server-side JSX components. components/client/index.tsx contains components hydrated in the browser. theme.css applies only to that deck. Local images are detected during compilation and rewritten to public URLs based on the mount path and deck slug.

Embedding

Use createDeckViewerEmbed() to embed multiple decks into the same document. This high-level helper builds an iframe viewer, controls, and a table of contents from a CompiledDeck already loaded by the application.

When embedding from an external blog, enable router.embed and list the allowed parent origins in frameAncestors. Iframe navigation does not require CORS, but it does require an appropriate CSP frame-ancestors directive.

router: {
  embed: {
    frameAncestors: ["https://blog.example.com"],
    robots: false,
  },
}
<iframe
  src="https://slides.example.com/decks/product/embed"
  title="Product deck"
  allow="fullscreen"
></iframe>

Browser export

Enable PDF and PNG exports by returning a Cloudflare Browser Rendering binding from the resolver. The viewer displays export controls only for requests accepted by authorize. Store export tokens as Wrangler secrets rather than in vars.

Cmd + P and Ctrl + P open the viewer's print route, which renders every slide for printing. Server-side export sends the same print route to Browser Rendering.

Build-time OGP recipe

Enable router.viewer.openGraph when generating share images at build time. The default image path is decks.paths(slug).ogImage. The viewer derives absolute Open Graph and Twitter Card URLs from the request origin.

export default defineDecksConfig({
  mountPath: "/decks",
  router: {
    viewer: { openGraph: true },
  },
});

Override imagePath with a resolver when images are served from a separate CDN. Set Open Graph configuration to false, or omit it, to suppress social metadata.

Image generation is intentionally not part of the core package. The examples/ogp recipe passes the manifest returned by compileDecks() from hono-decks/node to Satori and resvg, then stores 1200 by 630 PNG files in Workers Static Assets. Satori and resvg are example-only dependencies, and the recipe does not require Browser Rendering. Satori accepts TTF, OTF, and WOFF fonts but not WOFF2, so the build script must receive a font file that covers the target language.

build.ogpCacheFile stores external site metadata used by @card. It is separate from the viewer's share image.

Public entries

  • hono-decks: configuration, configured-kit types, deck authoring, viewer and embed customization, and source decorators
  • hono-decks/advanced: defineDecks(), decksRouter(), deckContext(), raw renderers, and other low-level pipeline APIs
  • hono-decks/client: browser hydration
  • hono-decks/node: the compiler and Node filesystem adapter
  • hono-decks/cli: the programmatic CLI runner

The generated workflow normally imports only the root entry. Use hono-decks/advanced only when assembling a custom DeckSource or router from the lower-level primitives.

Cloudflare Workers

Use a JSONC Wrangler config, a current compatibility date, and nodejs_compat when required. Generate binding types with wrangler types so handwritten environment types cannot drift from the deployed configuration.

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-decks",
  "main": "src/index.ts",
  "compatibility_date": "2026-07-14",
  "compatibility_flags": ["nodejs_compat"]
}

Advanced API

Use the advanced entry only when building a custom runtime from an existing manifest, database, or remote object store.

import { decksRouter, manifestDeckSource } from "hono-decks/advanced";

const source = manifestDeckSource(manifest);
app.route("/internal-slides", decksRouter({ source, dev: true }));

At this level, the application owns the mount path, source policy, and option merging. For normal use, the generated createDecks(config) API is shorter and safer.

Examples

  • examples/minimal: the smallest standalone Worker setup
  • examples/basic: R2, Browser Rendering, custom routes, and client components
  • examples/honox: HonoX integration