zfb

Type to search...

to open search from anywhere

defineConfig

CreatedJun 1, 2026Takeshi Takatsudo

Define a zfb project configuration with full type inference.

Signature

defineConfig(config: ZfbConfig): ZfbConfig

defineConfig is exported from zfb/config. The helper is identity-typed: it returns its argument unchanged. Its only job is to give your editor IntelliSense and type-checking against the ZfbConfig shape. The actual schema is enforced by Rust serde at config-load time, so the same rules apply whether you author your config in TypeScript or JSON.

Config shape

All keys are camelCase and mirror crates/zfb/src/config.rs.

  • outDir?: string — output directory. Default: "dist".
  • publicDir?: string — static assets directory, copied verbatim. Default: "public".
  • host?: string — dev/preview server bind host.
  • port?: number — dev/preview server port.
  • framework?: "preact" | "react" — JSX framework runtime. Default: "preact".
  • collections?: CollectionDef[] — content collections. Each entry has name (identifier used in getCollection calls), path (directory relative to the project root), and optional schema (validated at config-load time; frontmatter is checked against the schema by zfb check and the build).
  • tailwind?: { enabled?: boolean } — Tailwind options.
  • plugins?: PluginConfig[] — user-supplied plugins. Each entry has name (npm specifier or ./-relative path) and optional options (an arbitrary JSON object passed to the plugin’s hooks). See Plugins for the full hook contract — setup, preBuild, postBuild, devMiddleware — including virtual modules, import aliases, and dev-only injected routes.
  • adapter?: string — deploy-target adapter package name. Omit for a pure static build. A package like "@takazudo/zfb-adapter-cloudflare" wraps the SSR bundle into a deploy-ready entry (e.g. dist/_worker.js for Cloudflare Pages).
  • site?: string — canonical origin URL (e.g. "https://example.com"). When set, exposes globalThis.__zfb.site so layouts can build canonical <link> tags, OpenGraph meta, sitemap absolute hrefs, and hreflang alternates. Must be an absolute HTTP/HTTPS URL; omit for builds that do not need server-side canonical URL construction. Distinct from base — see below.
  • base?: string — public URL prefix for asset URLs. Use when the site is deployed under a sub-path (e.g. "/pj/my-site/"). Distinct from site: base prefixes asset URLs; site is the full canonical origin used in metadata.
  • stripMdExt?: boolean — strip .md/.mdx extensions from internal link hrefs during MDX compilation and append a trailing /. Default: false.
  • trailingSlash?: boolean — append a trailing / to extensionless absolute hrefs when rewriting base paths. Default: false.
  • resolveMarkdownLinks?: ResolveMarkdownLinksConfig — markdown link resolver settings. Enable to rewrite [label](./other.mdx) links to their rendered route URLs.
  • extraWatchPaths?: string[] — extra absolute filesystem paths the dev watcher follows in addition to the in-project source roots. See Watching paths outside the project root.

Watching paths outside the project root

extraWatchPaths lets zfb dev live-reload when files outside the project tree change — useful when a project reads content from a sibling repo, a file: dep that ships content alongside code, or a shared filesystem directory.

import { defineConfig } from "zfb/config";

export default defineConfig({
  extraWatchPaths: [
    "/home/me/knowledge-base",
    "/srv/shared-content",
  ],
});

Semantics:

  • Absolute paths only. Each entry must be an absolute path. Relative paths are rejected at config-load with an extraWatchPaths[N]: ... must be an absolute path error — the dev watcher registers each entry verbatim, outside the project root, so it has no anchor to resolve a relative path against.
  • Canonicalisation. Each entry is canonicalised (Path::canonicalize) once when zfb dev boots. Symlinks are resolved; downstream events reach the rebuild logic with the canonical form, so the path the watcher emits matches the form you’d see by running realpath on the configured value.
  • Missing-at-boot. If a configured path does not exist at the moment zfb dev starts, it is skipped with a warning. The watcher does not poll for the path to appear later — if you create the directory after the dev server is already running, restart zfb dev to pick it up.
  • Recursive. Each entry is watched recursively. Sub-directories created after boot are picked up automatically by the OS-level recursive watch.
  • Rebuild scope. Events from these paths fall outside the dependency graph’s coverage (the graph only tracks in-tree edges), so they conservatively trigger broader rebuilds than equivalent in-tree edits. The trade-off is intentional: correctness over precision for out-of-root sources.

Security note. Opt-in only — do not point this at unbounded directories like $HOME or /. On Linux the recursive watcher registers every subdirectory and can quickly hit the inotify max_user_watches ceiling (default ~8192 on many distributions) on a large tree. If you need to watch a sprawling source, watch the narrowest sub-tree that contains the files you actually edit.

This is a dev-mode feature. Production builds (zfb build) snapshot the filesystem once and do not rely on watcher events, so extraWatchPaths has no effect on shipped output.

Examples

// zfb.config.ts — the recommended form
import { defineConfig } from "zfb/config";

export default defineConfig({
  outDir: "dist",
  framework: "preact",
  collections: [
    {
      name: "blog",
      path: "content/blog",
    },
  ],
  tailwind: { enabled: true },
});

The loader accepts zfb.config.ts (preferred) and zfb.config.json (legacy fallback). When only zfb.config.json is present it is read via serde_json; plugin paths declared as ./..., ../..., or absolute paths are resolved relative to the config file (npm specifiers like "@takazudo/some-plugin" work in both forms).

// zfb.config.json — legacy form, still supported
{
  "outDir": "dist",
  "framework": "preact",
  "collections": [
    {
      "name": "blog",
      "path": "content/blog"
    }
  ],
  "tailwind": { "enabled": true }
}

Validation

The loader enforces the following rules and reports errors with the file path plus line:column for JSON parse failures:

  • Collection names must be unique.
  • path cannot be an absolute path.
  • path cannot contain .. segments that escape the project root.

Revision History