defineConfig
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/.
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 hasname(identifier used ingetCollectioncalls),path(directory relative to the project root), and optionalschema(validated at config-load time; frontmatter is checked against the schema byzfb checkand the build).tailwind?: { enabled?: boolean }— Tailwind options.plugins?: PluginConfig[]— user-supplied plugins. Each entry hasname(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/for Cloudflare Pages)._ worker. js site?: string— canonical origin URL (e.g."https:). When set, exposes/ / example. com" globalThis.__zfb.siteso 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 frombase— 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 fromsite:baseprefixes asset URLs;siteis the full canonical origin used in metadata.stripMdExt?: boolean— strip.md/.mdxextensions 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](.links to their rendered route URLs./ other. mdx) 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 patherror — 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 whenzfb devboots. 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 runningrealpathon the configured value. - Missing-at-boot. If a configured path does not exist at the moment
zfb devstarts, 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, restartzfb devto 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.
pathcannot be an absolute path.pathcannot contain..segments that escape the project root.