Content & SEO
Four features for shipping public-facing content — from raw editable strings to full tenant-branded pages — plus the site-discovery layer search engines and LLM answer engines need to find and cite them.
text-content
Section titled “text-content”Status: ✅ Stable
What: Generic Markdown key-value store — one read_text_blocks row per
(tenantId, slug, lang). The building block under legal-pages, and the
marketing-copy seam the apex-landing sample pulls its hero headline and
tagline from.
How it works: r.writeHandler/r.queryHandler convention CRUD, plus an
anonymous by-tenant listing query (visual-tree sidebar, marketing pages
that render editable blocks without a JWT). Cross-feature reads go through
extraContext.textContent (createTextContentApi), never a code import —
the same decoupling pattern every other feature in this bucket follows.
Example:
import { createTextContentFeature, createTextContentApi,} from "@cosmicdrift/kumiko-bundled-features/text-content";
await runDevApp({ features: [createTextContentFeature(), myFeature], extraContext: ({ db }) => ({ textContent: createTextContentApi(db) }),});legal-pages
Section titled “legal-pages”Status: ✅ Stable
What: Opt-in wrapper around text-content for DACH compliance — four
fixed public HTML routes (/legal/impressum, /legal/datenschutz,
/legal/imprint, /legal/privacy), Markdown → HTML rendering, and a
boot-time job that hard-fails in production when the required DE blocks
(imprint/de, privacy/de) aren’t seeded in SYSTEM_TENANT.
How it works: the routes call text-content:query:by-slug via an
internal app.fetch (never a code import), always scoped to
SYSTEM_TENANT_ID regardless of the resolved Host — one Impressum per app,
not per tenant.
Recipe: recipes-legal-pages — seeds
the required blocks and asserts every route end-to-end.
Example:
import { createLegalPagesFeature } from "@cosmicdrift/kumiko-bundled-features/legal-pages";
await runDevApp({ features: [createTextContentFeature(), createLegalPagesFeature(), myFeature], anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID },});managed-pages
Section titled “managed-pages”Status: ✅ Stable
What: Tenant-editable, server-rendered public pages — one Markdown
page per (tenantId, slug, lang), a published gate (drafts → 404), and
per-tenant branding (title/description/site URL/accent color/logo/layout
preset, optional tier-gated custom CSS).
How it works: an anonymous GET {basePath}/:slug route resolves the
tenant from the request Host via an app-supplied resolveApexTenant,
isolates content with Vary: Host, and ships TenantAdmin/SystemAdmin admin
screens (page-list/page-edit) backed by convention CRUD. Also exposes an
anonymous by-tenant-published listing query — SQL-filtered on published,
never leaking drafts — for site-discovery consumers like seo.
Recipe: recipes-managed-pages
Example:
import { createManagedPagesFeature } from "@cosmicdrift/kumiko-bundled-features/managed-pages";
await runDevApp({ features: [ createConfigFeature(), createManagedPagesFeature({ resolveApexTenant: (host) => resolveTenantIdFromHost(host), }), myFeature, ],});Status: 🚧 Beta
What: Site-discovery + SEO/AEO/GEO surface for apex/content pages — GET /sitemap.xml and GET /llms.txt (merging an app-supplied callback with
legal-pages’ fixed routes and/or managed-pages’ published slugs), an
opt-in GET /robots.txt, and an OG/JSON-LD/canonical extension for
wrapInLayout (the legal-pages/managed-pages render path). Also exports
pure schema.org JSON-LD builders — organizationSchema, webPageSchema,
faqPageSchema.
How it works: a boot-time job hard-fails in production when none of the
three entry sources (the sitemapEntries() callback, includeLegalPages,
managedPages) can supply anything — the routes would otherwise serve a
permanently empty document. Tenant-scoped config keys (organization
name/logo, Twitter handle, llms.txt summary, default OG image) feed the
Organization JSON-LD helper and the llms.txt summary line.
seo vs the apex renderer: if your marketing page goes through
renderApexPage, its head handling (Open Graph, Twitter card, canonical,
hreflang, JSON-LD) is already complete — seo only adds the site-discovery
routes and the schema builders there. The wrapInLayout({ seo }) extension
is for the legal-pages/managed-pages render path, which had no OG/JSON-LD
before.
Recipe: recipes-apex-landing —
organizationSchema/webPageSchema feeding ApexHead.schemaJson, plus a
separate integration test mounting createSeoFeature alongside the landing
route to exercise /sitemap.xml and /llms.txt as real HTTP requests.
Practical setup: this section covers what fields exist. For what to actually configure — config values, JSON-LD wiring, verifying the output — see Make your apex/content pages SEO/AEO/GEO discoverable.
Example:
import { createSeoFeature } from "@cosmicdrift/kumiko-bundled-features/seo";
await runDevApp({ features: [ createConfigFeature(), createLegalPagesFeature(), createSeoFeature({ sitemapEntries: (host) => [{ loc: `https://${host}/` }], includeLegalPages: true, }), myFeature, ],});