Configuration Reference

Furin reads furin.config.ts (or .js / .mjs) at the project root. Every field is optional — the framework works out of the box with zero config.

furin.config.ts
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,
  targets: ["bun"],
  plugins: [tailwind],
  bun: {
    compile: "embed",
  },
  static: {
    basePath: "/my-app",
    outDir: "dist",
    onSSR: "error",
  },
});

rootDir

Type: string
Default: "."

Project root directory. All relative paths in the config (e.g. pagesDir, serverEntry) are resolved from here.


pagesDir

Type: string
Default: "src/pages"

Directory containing your page files. Furin scans this directory recursively and turns every .tsx file into a route.


serverEntry

Type: string
Default: "src/server.ts"

Path to the server entrypoint that creates the Elysia instance. Required for bun target builds. Not needed for static builds.


clientLogging

Type: boolean Default: false

Inject the browser-side evlog logger into the generated hydration entry. Server-side request logging is unaffected by this option.

When disabled, Furin keeps evlog and evlog/http out of the browser bundle and uses a no-op logger for internal client events. Enable it when you want SPA navigation and custom browser events to be batched to /_furin/ingest and re-emitted server-side as service: "furin:browser".

Enabling it adds about 10 KB gzipped to the client bundle.

ts
import { defineConfig } from "@teyik0/furin/config";

export default defineConfig({
  clientLogging: true,
});

targets

Type: Array<"bun" | "node" | "vercel" | "cloudflare" | "static">
Default: ["bun"]

Build targets to produce. Only "bun" and "static" are fully implemented today. "node", "vercel", and "cloudflare" are planned.

TargetOutputUse case
bunServer bundle + client assetsSelf-hosted Bun runtime
staticPre-rendered HTML directoryGitHub Pages, Netlify, CDN

You can also pass "all" to the CLI (--target all) to build every target defined in the config.


plugins

Type: BunPlugin[]

Bun plugins passed to the client bundler. Useful for Tailwind CSS, MDX, or any custom transform.

ts
import { defineConfig } from "@teyik0/furin/config";
import tailwind from "bun-plugin-tailwind";

export default defineConfig({
  plugins: [tailwind],
});

buildOnly flag

Plugins can be marked buildOnly: true to skip runtime registration. Useful for build-time-only transforms like Tailwind.

ts
import type { FurinPlugin } from "@teyik0/furin/config";

const myPlugin: FurinPlugin = {
  name: "my-plugin",
  setup() { /* ... */ },
  buildOnly: true,
};

bun.compile

Type: "server" | "embed"
Default: undefined (no compilation)

Compile the server to a native Bun binary.

ModeBehaviour
"server"Binary + client/ directory on disk
"embed"Self-contained binary with all assets in memory
bash
# CLI override
bunx furin build --compile embed

static

Configuration for the static build target.

static.basePath

Type: string
Default: ""

Sub-path prefix for static deployments (e.g. "/my-app" for user.github.io/my-app). Must start with / and have no trailing slash.

static.outDir

Type: string
Default: "dist"

Output directory for the static export.

static.onSSR

Type: "error" | "skip"
Default: "error"

Behaviour when SSR or ISR routes are encountered during a static build:

  • "error" — Throw at build time with the list of incompatible routes.
  • "skip" — Emit a warning and omit those routes from the output.

Dev plugins (bunfig.toml)

Dev-only plugins are configured in bunfig.toml, not furin.config.ts:

toml
[serve.static]
plugins = ["bun-plugin-tailwind", "@teyik0/furin/strip-plugin"]
env = "FURIN_PUBLIC_*"

See the Plugins page for more details.


Runtime furin() options

The Elysia plugin also accepts runtime options in src/server.ts. These options affect how Furin is mounted in the application server, so they live next to your API plugin order rather than in furin.config.ts.

ts
import { furin } from "@teyik0/furin";
import { Elysia } from "elysia";
import { api } from "./api";

new Elysia()
  .use(await furin({ pagesDir: "./src/pages", sync: true }))
  .use(api)
  .listen(3000);

sync

Type: boolean

Enable Furin's built-in sync transport. Furin mounts SSE at /_furin/sync, exposes cursor catch-up at /_furin/sync/changes, and injects the client runtime automatically. API mutations using furinSync() are synchronized by default and may opt out with sync: false.

Synced mutations stay regular API routes. There are no server actions, generated mutation names, collections, or React wrapper providers required.

The current adapter is process-local memory: idempotency responses and changes are lost on restart and are not shared across replicas.

Comments