Configuration Reference
Production builds read the first furin.config.ts, furin.config.js, or furin.config.mjs in the working directory. defineConfig() provides inference; Furin validates serializable fields when the CLI loads the file. Unknown keys fail with their path and the closest valid key (for example, reactCompilor suggests reactCompiler), every plugin must be an object with a name string and a setup function, and every target must be a built-in name or a deployment adapter.
import { vercel } from "@teyik0/furin/adapters/vercel";
import { defineConfig } from "@teyik0/furin/config";
import tailwind from "bun-plugin-tailwind";
export default defineConfig({
rootDir: ".",
pagesDir: "src/pages",
serverEntry: "src/server.ts",
clientLogging: false,
reactCompiler: true,
plugins: [{ ...tailwind, buildOnly: true }],
bun: { compile: "embed" },
targets: ["bun", vercel({ regions: ["cdg1"] })],
});
CLI flags override the corresponding file settings. Paths are resolved from the CLI working directory, then pagesDir and serverEntry are resolved relative to rootDir.
Build Fields
| Field | Type | Default | Purpose |
|---|---|---|---|
rootDir | string | Current directory | Project root for build paths |
pagesDir | string | src/pages | Pages directory for one root-mounted app |
serverEntry | string | src/server.ts | Elysia entry for server builds |
apps | { pagesDir; prefix? }[] | Auto-detected | Explicit multi-instance build inputs |
clientLogging | boolean | false | Include the browser evlog HTTP drain |
reactCompiler | boolean | true | Enable Bun's React compiler in client builds |
optimizeImports | string[] | Bun defaults | Extra package barrels for Bun import optimization |
plugins | FurinPlugin[] | None | Bun build and route-import plugins |
preload.routeChunks | boolean | true | Emit <link rel="modulepreload"> for the current route's chunks |
bun.compile | "server" | "embed" | No executable | Compile the Bun server |
static | StaticExportConfig | See below | Configure pure static output |
targets | (BuildTarget | FurinAdapter)[] | ["bun"] | What furin build builds without --target; also where adapters receive their options |
apps takes precedence over pagesDir and the server entry. When neither is configured, a server build evaluates the server entry in a child process, with furin() only recording its { pagesDir, prefix }, and builds those apps, then falls back to src/pages. Any furin() call counts, whatever its import name or how its options are computed. If the server entry cannot be evaluated at build time, list the apps in apps.
Server builds then check the result against the application itself: Elysia AOT already evaluates the server entry, and Furin records every furin({ pagesDir, prefix }) call it makes. A mounted app the build did not produce, or a built app the server never mounts, fails the build and lists both sides. Relative pagesDir values resolve against rootDir. Applications using .mount() skip AOT and therefore this check.
export default defineConfig({
apps: [
{ pagesDir: "./src/pages" },
{ pagesDir: "./src/admin", prefix: "/admin" },
],
});
Use apps when mount values come from variables or factories that static scanning cannot resolve.
Client Logging
export default defineConfig({ clientLogging: true });
This includes evlog/http in the generated browser entry and sends browser events to the prefixed /_furin/ingest endpoint. Server request logging is configured separately through furin({ logger }). See Logging.
React Compiler And Import Optimization
export default defineConfig({
reactCompiler: true,
optimizeImports: ["legacy-ui-library"],
});
Bun's React compiler is enabled by default; disable it only to isolate a compiler incompatibility. Bun already optimizes packages declaring sideEffects: false, so list only additional pure barrel packages under optimizeImports.
Plugins
import type { FurinPlugin } from "@teyik0/furin/config";
const assetPlugin: FurinPlugin = {
name: "asset-plugin",
buildOnly: true,
setup(build) {
// Register Bun build hooks.
},
};
Production registers non-buildOnly plugins while importing routes, then forwards every plugin to the relevant Bun builds. Development client plugins live in bunfig.toml; server-side route imports may also require Bun.plugin(). See Plugins.
Bun Executables
export default defineConfig({ bun: { compile: "server" } });
| Value | Output |
|---|---|
| Omitted | Bundled server.js with assets on disk |
server | Native executable with client/public assets on disk |
embed | Native executable with framework client/public assets embedded |
The CLI --compile flag wins over this field.
Targets And Adapters
import { vercel } from "@teyik0/furin/adapters/vercel";
export default defineConfig({
targets: ["bun", vercel({ regions: ["cdg1"] })],
});
furin build builds every entry of targets. furin build --target vercel builds the configured entry named vercel, so its options apply; a name missing from targets uses the built-in target with default options.
vercel({ regions }) places the Function close to the application's database or upstream service. The editor suggests Vercel's supported compute regions; an empty list, duplicates, and unsupported codes fail while the configuration loads. See Deployment for the adapter contract.
Static Export
export default defineConfig({
static: {
basePath: "/docs",
outDir: "dist",
onSSR: "error",
},
});
| Field | Type | Default | Purpose |
|---|---|---|---|
basePath | string | "" | Hosting subpath, starting with / and without a trailing slash |
outDir | string | dist | Static output directory |
onSSR | "error" | "skip" | error | Fail on SSR/ISR routes or omit them with warnings |
Static export also rejects requestLoader() and dynamic SSG pages without staticParams() when onSSR is error.
Runtime furin() Options
Runtime options live beside Elysia composition, not in the build config:
await furin({
pagesDir: "./src/pages",
prefix: "/admin",
clientDir: "./custom-client",
clientLogging: true,
logger: { exclude: ["/health"] },
pageCache,
sync,
});
| Field | Purpose |
|---|---|
pagesDir | Runtime pages identity; defaults to src/pages |
prefix | Full physical path of pages, framework endpoints, public assets, and client assets; the client bundle is built for it |
clientDir | Override production client assets; build artifacts normally resolve this automatically |
clientLogging | Enable browser logging for this runtime/build context |
logger | evlog Elysia options plus process-wide sampling; a second instance with a different sampling throws |
cachePurger | CDN purge hook for this instance; receives its physical paths and takes precedence over setCachePurger() |
cacheTagPurger | CDN purge hook for this instance's semantic cache tags; receives the tags passed to revalidateTag() and takes precedence over the deployment adapter's tag purger |
pageCache | Optional shared store for SSG, ISR, and PPR public artifacts; when omitted, the existing process-local render caches remain active |
sync | Sync adapter, principal resolver, optional notifier, optional principal hash secret, and optional durable endpoint path |
Keep the same pagesDir and prefix between the build and runtime mount. prefix is the full physical path: mount Furin on an Elysia instance without its own prefix. Each furin() call serves one application. See Caching, Multi-Instance, and Sync & Invalidations.
For a single Bun process, omit pageCache. For multiple Bun replicas, create one redisPageCache() adapter at module scope and pass it to each furin() mount. On Vercel Build Output, omit it and use the cache integration installed by the deployment adapter. Vercel deployments reject a custom pageCache during application initialization so native and Redis caches cannot become competing sources of truth.