Skip to content

Core concepts

Seed and headers

The JSON document that bootstrap and visits share.

The seed is the only read model. Document GET places it in #__keel_seed. Visits return it as JSON (X-Keel-Visit: true). Bootstrap parses it and passes a typed context into mount().

{
  "v": 1,
  "page": "harbor.home",
  "path": "/",
  "params": {},
  "data": {
    "viewer": { "id": "ada", "displayName": "Ada" },
    "feed": []
  },
  "errors": {},
  "theme": { "id": "harbor", "version": "0.1.0" },
  "entry": "/__keel/pack/harbor/pages/harbor.home.js",
  "css": ["/__keel/pack/harbor/assets/mount.css"],
  "build": "9f1c6e0b…",
  "host": "#__keel_root",
  "head": {
    "title": "Harbor",
    "description": "An in-memory message board.",
    "canonical": "https://example.com/"
  }
}

Each adapter reads that payload from its page store (page() in Svelte, usePage() elsewhere). No second fetch.

Reading the seed
<script lang="ts">
import { page } from "@kolektiv/keel-svelte"
import type { HomePage } from "@app/page-types"

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

<p>{ctx.data.viewer?.displayName ?? "Anonymous"}</p>
<p>{ctx.path}</p>
<p>{ctx.page}</p>
<script>
import { page } from "@kolektiv/keel-svelte"

const ctx = page()
</script>

<p>{ctx.data.viewer?.displayName ?? "Anonymous"}</p>
<p>{ctx.path}</p>
<p>{ctx.page}</p>
import { usePage } from "@kolektiv/keel-react"
import type { HomePage } from "@app/page-types"

export default function Whoami() {
const seed = usePage<HomePage>()
return (
  <>
    <p>{seed.data.viewer?.displayName ?? "Anonymous"}</p>
    <p>{seed.path}</p>
    <p>{seed.page}</p>
  </>
)
}
import { usePage } from "@kolektiv/keel-react"

export default function Whoami() {
const seed = usePage()
return (
  <>
    <p>{seed.data.viewer?.displayName ?? "Anonymous"}</p>
    <p>{seed.path}</p>
    <p>{seed.page}</p>
  </>
)
}
<script setup lang="ts">
import { usePage } from "@kolektiv/keel-vue"
import type { HomePage } from "@app/page-types"

const seed = usePage<HomePage>()
</script>

<template>
<p>{{ seed.data.viewer?.displayName ?? "Anonymous" }}</p>
<p>{{ seed.path }}</p>
<p>{{ seed.page }}</p>
</template>
<script setup>
import { usePage } from "@kolektiv/keel-vue"

const seed = usePage()
</script>

<template>
<p>{{ seed.data.viewer?.displayName ?? "Anonymous" }}</p>
<p>{{ seed.path }}</p>
<p>{{ seed.page }}</p>
</template>
import { usePage } from "@kolektiv/keel-solid"
import type { HomePage } from "@app/page-types"

export default function Whoami() {
const seed = usePage<HomePage>()
return (
  <>
    <p>{seed().data.viewer?.displayName ?? "Anonymous"}</p>
    <p>{seed().path}</p>
    <p>{seed().page}</p>
  </>
)
}
import { usePage } from "@kolektiv/keel-solid"

export default function Whoami() {
const seed = usePage()
return (
  <>
    <p>{seed().data.viewer?.displayName ?? "Anonymous"}</p>
    <p>{seed().path}</p>
    <p>{seed().page}</p>
  </>
)
}
import { usePage } from "@kolektiv/keel-preact"
import type { HomePage } from "@app/page-types"

export default function Whoami() {
const seed = usePage<HomePage>()
return (
  <>
    <p>{seed.data.viewer?.displayName ?? "Anonymous"}</p>
    <p>{seed.path}</p>
    <p>{seed.page}</p>
  </>
)
}
import { usePage } from "@kolektiv/keel-preact"

export default function Whoami() {
const seed = usePage()
return (
  <>
    <p>{seed.data.viewer?.displayName ?? "Anonymous"}</p>
    <p>{seed.path}</p>
    <p>{seed.page}</p>
  </>
)
}
import { html } from "lit"
import { KeelElement } from "@kolektiv/keel-lit"
import type { HomePage } from "@app/page-types"

export default class Whoami extends KeelElement<HomePage> {
render() {
  return html`
    <p>${this.page.data.viewer?.displayName ?? "Anonymous"}</p>
    <p>${this.page.path}</p>
    <p>${this.page.page}</p>
  `
}
}
import { html } from "lit"
import { KeelElement } from "@kolektiv/keel-lit"

export default class Whoami extends KeelElement {
render() {
  return html`
    <p>${this.page.data.viewer?.displayName ?? "Anonymous"}</p>
    <p>${this.page.path}</p>
    <p>${this.page.page}</p>
  `
}
}
import { Component } from "@angular/core"
import { injectKeelPage } from "@kolektiv/keel-angular"
import type { HomePage } from "@app/page-types"

@Component({
selector: "app-whoami",
standalone: true,
template: `
  <p>{{ page().data.viewer?.displayName ?? "Anonymous" }}</p>
  <p>{{ page().path }}</p>
  <p>{{ page().page }}</p>
`,
})
export default class Whoami {
readonly page = injectKeelPage<HomePage>()
}
import { Component } from "@angular/core"
import { injectKeelPage } from "@kolektiv/keel-angular"

@Component({
selector: "app-whoami",
standalone: true,
template: `
  <p>{{ page().data.viewer?.displayName ?? "Anonymous" }}</p>
  <p>{{ page().path }}</p>
  <p>{{ page().page }}</p>
`,
})
export default class Whoami {
readonly page = injectKeelPage()
}

theme is the serving pack’s { id, version }; entry, css, and build come from that pack. build is the pack’s content hash (also X-Keel-Build); a client whose mounted build differs reloads itself — see Pack hot reload. Optional fields: shared, layout, redirect, head. path is absolute. page and entry are non-blank.

On a document GET the host writes head into the HTML shell. Visits return the same head so <Head> stays in sync. See Document head and SEO.

Partial reloads (only / except) filter top-level data keys on visits only — see Partial reloads. The field list and X-Keel-* headers are on the wire protocol.