Skip to content

Getting started

Pack

Scaffold a pack from /__keel/schema for any adapter, or wire keelPack in Vite by hand.

This is the frontend half. A pack implements page ids. It never owns paths. Two ways in: scaffold from a running host, or Vite + keelPack from a blank tree. Packs can target any of the seven adapters — Svelte, React, Vue, Solid, Preact, Lit, or Angular. Svelte is the default and the examples below use it; see Framework adapters for the per-framework APIs and build caveats.

Have a host answering GET /__keel/schema first — Host (Ktor).

Option A — keel-scaffold

The host is the source of truth. Scaffold writes blank pages for every id in the schema, plus page-types.ts, Vite, and keelPack.

pnpm add -D @kolektiv/keel-pack

Pick the adapter with --framework. Omit it and you get Svelte.

pnpm exec keel-scaffold 127.0.0.1:8090 ./pack --id app --framework svelte
cd pack && pnpm install && pnpm build

You get +page.svelte / +layout.svelte, +page.ts for id overrides, and +head.svelte.

pnpm exec keel-scaffold 127.0.0.1:8090 ./pack --id app --framework react
cd pack && pnpm install && pnpm build

Swap react for preact or solid. Pages are +page.tsx / +layout.tsx, id in +page.ts, head in +head.html.

pnpm exec keel-scaffold 127.0.0.1:8090 ./pack --id app --framework vue
cd pack && pnpm install && pnpm build

Pages are +page.vue / +layout.vue, id in +page.ts, head in +head.html.

pnpm exec keel-scaffold 127.0.0.1:8090 ./pack --id app --framework lit
cd pack && pnpm install && pnpm build

Swap lit for angular. Pages are +page.ts / +layout.ts, id override in +page.id.ts, head in +head.html. Angular also emits tsconfig.app.json and pins typescript@~5.8.3.

localhost / 127.* use http://; other hosts default to https://. --framework accepts angular, lit, preact, react, solid, svelte (the default), and vue; --id names the pack (default: the output directory), --version pins the manifest version (default 0.1.0), and --schema <file.json> scaffolds from a saved contract with no host running. Re-run with --force to overwrite.

File layout by adapter (same id-keyed folders):

Framework Page / layout Id override Head template
Svelte +page.svelte, +layout.svelte +page.ts +head.svelte
React +page.tsx, +layout.tsx +page.ts +head.html
Preact +page.tsx, +layout.tsx +page.ts +head.html
Solid +page.tsx, +layout.tsx +page.ts +head.html
Vue +page.vue, +layout.vue +page.ts +head.html
Lit +page.ts, +layout.ts +page.id.ts +head.html
Angular +page.ts, +layout.ts +page.id.ts +head.html

Lit and Angular pages are themselves TypeScript modules, so their id override moves to +page.id.ts. Angular scaffolds also emit tsconfig.app.json with noEmit: false and pin typescript@~5.8.3; Angular 19 rejects TypeScript ≥ 5.9. Point the host at dist/<id>.feb. pnpm dev runs the same build in watch mode; a host with watchPacks = true swaps the result — see Pack hot reload.

Option B — Vite plugin by hand

Same result, files you type. After Client setup:

Option B below uses the Svelte Vite plugin. For other adapters, pair keelPack with that framework’s Vite plugin from Client setup, and set framework in keelPack({ … }) to match.

vite.config.ts
import { svelte } from "@sveltejs/vite-plugin-svelte"
import { keelPack } from "@kolektiv/keel-pack/vite"
import { defineConfig } from "vite"

export default defineConfig({
plugins: [
  svelte(),
  keelPack({
    id: "app",
    version: "0.1.0",
    framework: "svelte",
    pagesDir: "src/pages",
    bootstrap: "src/bootstrap.ts",
    contract: "src/lib/page-types.json",
    notFound: "app.notFound",
    pack: "dist/app.feb",
  }),
],
})
import { svelte } from "@sveltejs/vite-plugin-svelte"
import { keelPack } from "@kolektiv/keel-pack/vite"
import { defineConfig } from "vite"

export default defineConfig({
plugins: [
  svelte(),
  keelPack({
    id: "app",
    version: "0.1.0",
    framework: "svelte",
    pagesDir: "src/pages",
    bootstrap: "src/bootstrap.ts",
    contract: "src/lib/page-types.json",
    notFound: "app.notFound",
    pack: "dist/app.feb",
  }),
],
})

pagesDir is keyed by id, not URL. src/pages/app/home/+page.svelte is app.home. A sibling +page.ts may export const id = "app.home" if the folder name should not be the id.

src/bootstrap.ts
import { bootstrap } from "@kolektiv/keel-svelte"

void bootstrap()
import { bootstrap } from "@kolektiv/keel-svelte"

void bootstrap()
src/pages/app/home/+page.svelte
<script lang="ts">
import { Head, page } from "@kolektiv/keel-svelte"
import type { HomePage } from "../../../lib/page-types"

const ctx = page<HomePage>()
</script>

<Head />
<h1>{ctx.data.greeting}</h1>
<script>
import { Head, page } from "@kolektiv/keel-svelte"

const ctx = page()
</script>

<Head />
<h1>{ctx.data.greeting}</h1>

Copy page-types.ts / .json from GET /__keel/schema (scaffold does this) or from Gradle generateKeelTypes. Point contract at the JSON so a pack that implements an unknown id fails the build.

pnpm build
# dist/app.feb  →  FrontendBundle.fromFile(Path.of("pack/dist/app.feb"))

Load it on the host

fromFile / fromDirectory
val prod = FrontendBundle.fromFile(Path.of("pack/dist/app.feb"))
val dev = FrontendBundle.fromDirectory(Path.of("pack/dist"))

Then register a page if you have not already, and restart the host. Document GET should show the pack UI; a visit returns the seed JSON.