# @takazudo/zfb

> Content-site engine on the web's Request/Response model — prerender to static HTML, serve on Cloudflare Workers, or run as a local content server inside your app. One Rust binary. SDK with islands, content collections, pagination, and config helpers.

Latest version **2.20.0** (published 2026-09-19) · MIT license · 0 weekly downloads

## Install

```sh
npm install @takazudo/zfb
pnpm add @takazudo/zfb
yarn add @takazudo/zfb
bun add @takazudo/zfb
```

## Health

**Score 55/100 (C)** — status: active.

Positive: no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types; no esm support.

## Facts

| | |
|---|---|
| Version | 2.20.0 |
| Published | 2026-09-19 |
| First published | 2026-05-24 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 0 |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 0 |
| Author | Takeshi Takatsudo <takazudo@gmail.com> (https://github.com/Takazudo) |
| Maintainers | takazudo |
| Keywords | zfb, static-site-generator, ssg, islands, mdx, content-collections, preact, react, rust |

## Links

- npm: https://www.npmjs.com/package/@takazudo/zfb
- Repository: https://github.com/Takazudo/zudo-front-builder
- Homepage: https://takazudomodular.com/pj/zudo-front-builder/
- Issues: https://github.com/Takazudo/zudo-front-builder/issues
- npm.io page: https://npm.io/package/@takazudo/zfb

## Alternatives

- [mobx-react](https://npm.io/package/mobx-react.md) — 2.8M weekly downloads
- [rc-tree](https://npm.io/package/rc-tree.md) — 2.6M weekly downloads
- [@react-oauth/google](https://npm.io/package/@react-oauth/google.md) — 1.3M weekly downloads
- [@wagmi/connectors](https://npm.io/package/@wagmi/connectors.md) — 877.0K weekly downloads
- [vee-validate](https://npm.io/package/vee-validate.md) — 836.4K weekly downloads

## Recent versions

- 2.20.0 (latest) — 2026-09-19
- 2.19.0 — 2026-09-19
- 2.18.0 — 2026-09-15
- 2.17.0 — 2026-09-14
- 2.16.0 — 2026-09-07
- 2.15.1 — 2026-09-04
- 2.15.0 — 2026-09-03
- 2.14.3 — 2026-09-02
- 2.14.2 — 2026-08-31
- 2.14.1 — 2026-08-31
- 2.14.0 — 2026-08-30
- 2.13.1 — 2026-08-28
- 2.13.0 — 2026-08-27
- 2.12.0 — 2026-08-26
- 2.11.0 — 2026-08-25
- … 115 more at https://npm.io/package/@takazudo/zfb/versions

## README

# @takazudo/zfb

> A content-site engine built on the web's Request/Response model. Write pages in TSX and MDX once, then prerender them to static HTML, serve them on Cloudflare Workers, or run the engine as a local content server inside your own app. Ships as one Rust binary.

The public SDK module for [zfb][zfb-site]: islands, content collections,
pagination, config, plugins, and frontmatter helpers. User pages reach this
package through the bare specifier `"zfb"` — the build pipeline aliases that
specifier to `@takazudo/zfb` so user TSX can write:

```tsx
import { Island } from "zfb";
```

Full documentation: <https://takazudomodular.com/pj/zudo-front-builder/>.
Source: <https://github.com/Takazudo/zudo-front-builder>.

[zfb-site]: https://takazudomodular.com/pj/zudo-front-builder/

## Install

```sh
npm install @takazudo/zfb
# or: pnpm add @takazudo/zfb
# or: yarn add @takazudo/zfb
```

## What lives here

This package is the canonical TypeScript source for the `zfb` SDK
surface. Today it covers:

- `<Island when="visible|idle|load|media" media="...">` — JSX wrapper
  that marks a region for client-side hydration. Passing `ssrFallback`
  switches to the SSR-skip marker (`data-zfb-island-skip-ssr`).
- `scheduleHydrate(target, when, fire)` — the runtime branching helper
  consumed by the hydration runtime.
- `mountIslands(manifest)`, `mountNewIslands()`,
  `cancelPendingIslands()`, and `unmountIslands(...)` — public island
  lifecycle helpers used by the generated islands runtime and by the
  client router after body swaps.
- `When`, `WHEN_VALUES`, `DEFAULT_WHEN`, `isWhen`, `resolveWhen` — type
  and runtime utilities pinning the spelling of the four modes.
- `getCollection(name)`, `getEntry(name, slug)`, and
  `parseFrontmatter(raw)` — content collection helpers exported from
  `zfb/content`. `parseFrontmatter` is part of the public SDK surface so
  consumers can write custom content loaders that reuse the v0
  frontmatter parser without re-implementing it.
- `defaultComponents` — eleven-entry per-element override map (`h2`, `h3`,
  `h4`, `p`, `a`, `strong`, `blockquote`, `ul`, `ol`, `table`, `code`)
  ported from zudo-doc's `htmlOverrides` convention. **`h1` is deliberately
  omitted** because page titles render `<h1>` from frontmatter. Each entry
  is a thin passthrough and is also exported as a named const
  (`ContentParagraph`, `ContentLink`, …) so consumers can tree-shake-import a
  single override. Spread into a `components` prop to compose with custom
  overrides:

  ```tsx
  import { defaultComponents } from "zfb";

  <entry.Content components={{ ...defaultComponents, h2: MyFancyH2 }} />
  ```
- `mergeMdxComponents(globalSlot, perCall)` — precedence merge helper for
  the MDX component map (`defaultComponents` < global slot < per-call
  overrides).
- `paginate(items, opts)`, plus `PaginatedPage<T>` / `PaginateRoute<T>` —
  exported from `zfb/paginate`.
- `defineConfig(config)` — exported from `zfb/config` for the
  `zfb.config.ts` form (the recommended way to author a zfb project's
  configuration; the back-compat `zfb.config.json` form is still
  supported).
- `definePlugin(plugin)` — identity helper exported from `zfb/plugins`
  and the root barrel so plugin authors get typed lifecycle hooks without
  changing runtime behavior.
- `clientScript(name)` — SSR helper that returns the stable URL for a
  named client-script asset.
- `slugify(input)` and `SlugAllocator` — exported from the root barrel and
  the `zfb/slugify` subpath for heading-id parity with the Rust content
  pipeline.

The package is JSX-runtime-agnostic: the `Island` component does not
import preact or react, so it works under either framework adapter
without bundling the wrong runtime. `react` is listed as a peer
dependency but is **optional** (`peerDependenciesMeta.react.optional`)
— a preact/compat-only consumer does not need `react` installed and
does not need `auto-install-peers=true`.

## Usage

```tsx
import { Island } from "zfb";
import { Counter } from "../components/Counter.tsx"; // a "use client" component

export default function Page() {
  return (
    <>
      <h1>Welcome</h1>

      {/* Hydrate immediately on page load (default). */}
      <Island>
        <Counter />
      </Island>

      {/* Hydrate during the next idle callback. */}
      <Island when="idle">
        <Counter />
      </Island>

      {/* Hydrate only when the island first scrolls into view. */}
      <Island when="visible">
        <Counter />
      </Island>

      {/* Hydrate when a CSS media query first matches. */}
      <Island when="media" media="(max-width: 720px)">
        <Counter />
      </Island>

      {/* Skip SSR for the heavy child and render a placeholder instead. */}
      <Island when="idle" ssrFallback={<div>Loading…</div>}>
        <Counter />
      </Island>
    </>
  );
}
```

## The four `when=` modes

| `when`      | Trigger                                                        | Fallback                                           |
| ----------- | -------------------------------------------------------------- | -------------------------------------------------- |
| `"load"`    | Synchronous, immediate fire after registration. **Default.**   | n/a                                                |
| `"idle"`    | `requestIdleCallback`                                         | `setTimeout(0)` when not available                 |
| `"visible"` | `IntersectionObserver`, threshold 0, first intersection only   | Immediate fire when `IntersectionObserver` is missing |
| `"media"`   | `matchMedia(media)`, first matching change only                | Immediate fire when `matchMedia` or `media` is missing |

Unknown values produce a `console.warn` in development builds and fall
back to `"load"`.

`when="media"` requires a `media` prop containing a CSS media query
string. Supplying `media` with any other `when` value is ignored and
warns in development builds.

## Build-time output

The wrapper is intentionally type-erased at the JSX boundary. It reads
the wrapped child's JSX type identity (`displayName`, then `name`, then
host tag name) and writes the component name immediately. At the call
site, `<Island when="visible"><Counter count={1} /></Island>` renders as:

```html
<div data-zfb-island="Counter" data-when="visible" data-props='{"count":1}'>
  <!-- rendered child output -->
</div>
```

`data-props` carries the wrapped child's serializable own props across
the SSR-to-hydration boundary. The wrapper omits `children`, omits the
attribute entirely when there is no useful props payload, and the runtime
falls back to `{}` when the attribute is missing or malformed.

When `ssrFallback` is supplied, the heavy child is not rendered at SSR
time. The wrapper writes `data-zfb-island-skip-ssr="ComponentName"`
instead of `data-zfb-island`, still includes `data-when` / `data-media`
and `data-props` when applicable, and renders the fallback markup inside
the wrapper. The client runtime treats that marker as a render target
rather than a hydration target.

## Runtime helper

The hydration runtime imports (or inlines) `scheduleHydrate` from this
package:

```ts
import {
  scheduleHydrate,
  mountIslands,
  mountNewIslands,
  cancelPendingIslands,
  unmountIslands,
} from "@takazudo/zfb/runtime";

for (const el of document.querySelectorAll<HTMLElement>("[data-zfb-island]")) {
  const when = el.getAttribute("data-when") ?? "load";
  scheduleHydrate(el, when, () => hydrateOne(el));
}
```

`scheduleHydrate` returns a `cancel` function that aborts the schedule
if hydration has not fired yet. After firing, calling `cancel` is a
no-op.

`mountIslands(manifest)` captures the generated island manifest and
mounts both hydrated markers (`data-zfb-island`) and SSR-skip markers
(`data-zfb-island-skip-ssr`). `mountNewIslands()` re-walks the current
document after a client-router body swap using that captured manifest.
`cancelPendingIslands()` cancels deferred `idle` / `visible` / `media`
schedules before a swap, and `unmountIslands(root, incomingBody)` runs
framework cleanup for discarded islands while preserving matching
`data-zfb-transition-persist` islands.

### Post-mount marker

The runtime writes `data-zfb-island-mounted` after the generated `mount()` function
returns. It applies to both hydrated `data-zfb-island` markers and SSR-skip
`data-zfb-island-skip-ssr` markers. Consumers can gate pre-hydration styling before the
runtime's mount function returns, with the practical CSS selector:

```css
[data-zfb-island]:not([data-zfb-island-mounted]) {
  opacity: 0.7;
}
```

This means **"the runtime called the mount function and it returned"**, not **"the
component is interactive"**. React's `hydrateRoot` is internally concurrent, and the
generated `mount()` can silently no-op, so the attribute is a lifecycle signal rather
than proof that interaction is ready.

The marker is removed when an island is unmounted. During a body swap, an unchanged
`data-zfb-transition-persist` island keeps its mounted instance and marker when the
same id exists in the incoming body; a discarded island has its marker cleared and is
mounted again from the incoming markup. A props-changed persisted island loses the
marker while its old instance is torn down and receives it again after the forced
remount returns. A fresh runtime module strips stale markers from elements it has not
mounted before, then writes its own marker after mounting.

## Markdown / GFM config

`ZfbConfig.markdown.gfm` controls which GitHub-Flavored-Markdown
constructs the MDX parser recognises. The field accepts three shapes:

1. **Shorthand boolean** — turn every GFM construct on or off in one
   step. Use this when you want the full GFM surface.

   ```ts
   // zfb.config.ts
   import { defineConfig } from "zfb/config";

   export default defineConfig({
     markdown: {
       gfm: true, // strikethrough + table + autolink-literal + task-list-item + footnote-definition
     },
   });
   ```

2. **Partial object** — toggle individual constructs. Fields you omit
   fall back to the conservative default (`strikethrough: true`,
   `table: true`, `autolinkLiteral: true`, everything else off).

   ```ts
   // zfb.config.ts
   import { defineConfig } from "zfb/config";

   export default defineConfig({
     markdown: {
       gfm: {
         strikethrough: true,
         table: true,
         autolinkLiteral: false,    // explicit opt-out
         taskListItem: false,
         footnoteDefinition: false,
       },
     },
   });
   ```

3. **Omitted entirely** — the parser uses the conservative default.
   `~~text~~` parses as `<del>text</del>`, pipe tables render as
   `<table>`, and bare URLs like `https://example.com` become links;
   task lists and footnote definitions stay off.

   ```ts
   export default defineConfig({
     // no `markdown` field — strikethrough + table + autolink literals on,
     // task lists + footnotes off
   });
   ```

The five constructs you can toggle are: `strikethrough`, `table`,
`autolinkLiteral`, `taskListItem`, `footnoteDefinition`.

Projects that previously relied on raw `~~text~~` passing through as
literal characters should set `markdown: { gfm: { strikethrough: false } }`
or `markdown: { gfm: false }` to restore the old behaviour.

## Tests

```sh
pnpm --filter @takazudo/zfb test
```

The tests run under [vitest][vitest] with [happy-dom][happy-dom] as the
DOM implementation; no real browser is required.

[vitest]: https://vitest.dev/
[happy-dom]: https://github.com/capricorn86/happy-dom

---
_Source: https://npm.io/package/@takazudo/zfb · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
