Deployment

Furin supports two deployment strategies: a Bun build (live server with SSR/ISR support) and a static export (pre-rendered HTML files, no server required).

Static Export — GitHub Pages

The static export pre-renders all SSG routes to dist/path/index.html files and generates a 404.html SPA shell so that client-side navigation keeps working on GitHub Pages.

Configuration

Add a static block to furin.config.ts:

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

export default defineConfig({
  plugins: [...],
  static: {
    basePath: "/my-repo",  // GitHub repository name, no trailing slash
    outDir: "dist",         // output directory (default: "dist")
    onSSR: "error",         // "error" (default) | "skip"
  },
});
OptionTypeDefaultDescription
basePathstring""Path prefix for GitHub Pages (e.g. "/furin" when the site lives at user.github.io/furin/)
outDirstring"dist"Output directory
onSSR"error" | "skip""error"Behavior when an SSR route is encountered: hard error or warn + silent skip

Build Command

{
  "scripts": {
    "build:static": "furin build --target static"
  }
}

Output Layout

text
dist/
├── index.html              ← route "/"
├── blog/
│   └── hello-world/
│       └── index.html      ← route "/blog/hello-world"
├── 404.html                ← SPA shell (GitHub Pages fallback)
└── _client/
    ├── _hydrate.js
    └── _hydrate.css

SSR / ISR Routes

The static export does not support SSR or ISR routes — they require a live server.

  • onSSR: "error" (default) — the build fails and lists all non-exportable routes. Recommended to prevent silent production gaps.
  • onSSR: "skip" — SSR/ISR routes are skipped with a warning. Useful when some pages are served through a separate mechanism.
bash
# Example error output with onSSR: "error"
 Static export failed: the following routes cannot be statically exported:
  - /dashboard (mode=ssr)
  - /feed      (mode=isr)
Set onSSR: "skip" in furin.config.ts to skip them instead.

Deploying to GitHub Pages

Add a workflow file at .github/workflows/deploy.yml:

yaml
name: Deploy to GitHub Pages
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: ${{ steps.deploy.outputs.page_url }}
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install
      - run: bun run build:static
      - uses: actions/upload-pages-artifact@v3
        with:
          path: dist
      - id: deploy
        uses: actions/deploy-pages@v4

Tip: the generated 404.html is an empty SPA shell. GitHub Pages serves it automatically for any unknown URL, which lets the client-side router take over.


Bun Build (Live Server)

Use the Furin CLI through your package script or directly:

{
  "scripts": {
    "build": "bunx furin build --target bun"
  }
}

Output Layout

The Bun build writes to .furin/build/bun:

text
.furin/build/bun/
├── client/
│   └── index.html
├── public/
├── server.js
└── manifest.json

Compiled Binaries

If you pass --compile server, Furin writes a server binary and keeps the built client assets on disk.

bash
bunx furin build --target bun --compile server

If you pass --compile embed, Furin writes a self-contained server binary and removes the built client directory from the target output because the assets are embedded.

bash
bunx furin build --target bun --compile embed

Production Runtime

For disk-based builds:

  • server.js reads client assets from client/ next to the server bundle
  • public/ is copied into .furin/build/bun/public
  • FURIN_CLIENT_DIR can override the client asset directory at runtime
bash
FURIN_CLIENT_DIR=/absolute/path/to/client bun ./.furin/build/bun/server.js

Planned Targets

The config surface already names node, vercel, and cloudflare, but the current core build path throws for those targets. Treat them as planned, not available, until their builders land in packages/core/src/build/index.ts.

Public Environment Variables

Prefix client-visible variables with FURIN_PUBLIC_ and expose them through Bun:

toml
[serve.static]
env = "FURIN_PUBLIC_*"
bash
FURIN_PUBLIC_API_URL=https://api.example.com

Giscus In The Docs App

The docs app can render Giscus comments from GitHub Discussions by setting these public values:

bash
FURIN_PUBLIC_GISCUS_REPO=teyik0/furin
FURIN_PUBLIC_GISCUS_REPO_ID=...
FURIN_PUBLIC_GISCUS_CATEGORY=General
FURIN_PUBLIC_GISCUS_CATEGORY_ID=...

That keeps comments out of your application database.

Comments