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.

furin.config.ts
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

FieldTypeDefaultPurpose
rootDirstringCurrent directoryProject root for build paths
pagesDirstringsrc/pagesPages directory for one root-mounted app
serverEntrystringsrc/server.tsElysia entry for server builds
apps{ pagesDir; prefix? }[]Auto-detectedExplicit multi-instance build inputs
clientLoggingbooleanfalseInclude the browser evlog HTTP drain
reactCompilerbooleantrueEnable Bun's React compiler in client builds
optimizeImportsstring[]Bun defaultsExtra package barrels for Bun import optimization
pluginsFurinPlugin[]NoneBun build and route-import plugins
preload.routeChunksbooleantrueEmit <link rel="modulepreload"> for the current route's chunks
bun.compile"server" | "embed"No executableCompile the Bun server
staticStaticExportConfigSee belowConfigure 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.

ts
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

ts
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

ts
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

ts
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

ts
export default defineConfig({ bun: { compile: "server" } });
ValueOutput
OmittedBundled server.js with assets on disk
serverNative executable with client/public assets on disk
embedNative executable with framework client/public assets embedded

The CLI --compile flag wins over this field.

Targets And Adapters

ts
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

ts
export default defineConfig({
  static: {
    basePath: "/docs",
    outDir: "dist",
    onSSR: "error",
  },
});
FieldTypeDefaultPurpose
basePathstring""Hosting subpath, starting with / and without a trailing slash
outDirstringdistStatic output directory
onSSR"error" | "skip"errorFail 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:

ts
await furin({
  pagesDir: "./src/pages",
  prefix: "/admin",
  clientDir: "./custom-client",
  clientLogging: true,
  logger: { exclude: ["/health"] },
  pageCache,
  sync,
});
FieldPurpose
pagesDirRuntime pages identity; defaults to src/pages
prefixFull physical path of pages, framework endpoints, public assets, and client assets; the client bundle is built for it
clientDirOverride production client assets; build artifacts normally resolve this automatically
clientLoggingEnable browser logging for this runtime/build context
loggerevlog Elysia options plus process-wide sampling; a second instance with a different sampling throws
cachePurgerCDN purge hook for this instance; receives its physical paths and takes precedence over setCachePurger()
cacheTagPurgerCDN 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
pageCacheOptional shared store for SSG, ISR, and PPR public artifacts; when omitted, the existing process-local render caches remain active
syncSync 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.

Comments