Microsites: owner-written pages the instance builds and hosts
Parent: key-designs · the builder's claim protocol lives in builder-claim-skip-locked and is not repeated here.
Verdict (2026-09-07): a microsite is a React page the owner writes against the StandMeet SDK (software development kit), stored as source in Postgres, compiled by the separate builder service, and served by the instance at
/p/<slug>/— or at/when its slug is the reservedhome. The owner never touches deploy infrastructure: a page is a row plus a build pointer, so going live, staging, and rollback are pointer swaps (live_build_id / staging_build_id / previous_live_build_id,backend/db/schema.sql:734). Everything a page needs from the instance — the corpus, the agent, a pool asset, its own key-value store, the visitor's access code — arrives through SDK widgets and hooks, so the page composes what it cannot own. The surface was called "custom pages" until 2026-09-05 (rename e7fe80e91 + f6ffcaaa7, migration2026-09-05-microsites-rename.sql).
The serve path
- One serve core, two addresses.
MicrositeHandlers.Mount(routes/public/microsites.go:54) registers/microsites/{slug}and/homepage; both callserveSlugAt, which differs only in the slug it resolves and the<base href>it injects (/p/<slug>/at line 105,/at line 114). The Next.js app reverse-proxies/p/:slugto it inbeforeFilesrewrites (app/next.config.ts:71). File serving is shared with the admin preview (microsite_serve.go) so path-escape validation exists exactly once. - Injected head, read fresh per request.
pageHead.tags()(microsites.go:172) writes the base, the SEO block, and<meta name="standmeet-page-byoai">; nothing is snapshotted into the built page, and the response carriesCache-Control: no-cache, no-storeso a rolled-back page stops opening immediately. - Staging is a path credential.
/p/{slug}is live only; the staging build is reachable through the preview route (microsite_preview.go), where the credential travels in the path because a query string on<base href>would break relative assets.
The homepage is a microsite
The instance root is the reserved slug home (HomepageSlug, owner/usecase/microsite.go:34; a5e1cada9 on 2026-09-04, template 77aee7dc6). The app middleware probes GET /api/v1/homepage and rewrites / to it when a home build is live (app/src/middleware.ts:27); on 404 it falls through to the coded route, whose fallback branch renders DefaultHome from code (app/src/app/visitor-root.tsx:48, app/src/app/default-home.tsx). DefaultHome is itself composed of the same SDK widgets (AgentWidget, CorpusWidget, GateWidget, PageNavWidget), so an unedited instance and an edited one render through one vocabulary (28f750caf). Since 185c4321b (2026-09-07) nothing is materialized at claim — InstallHomepage: nil in backend/cmd/server/boot_http.go:248 — so no stored starter can freeze behind later SDK changes. The middleware never rewrites a /?code= or /?q= view: a coded visitor must reach the name picker, and a codeless question must hand off to /gate. The admin section gives the homepage its own edit entry and card, outside the pages table (38bfe3a2f, 06ab844de).
The editor: a mini-IDE that always builds
Each page has its own editor route (app/src/app/admin/microsites/page.tsx → components/admin/sections/microsites/PageEditor.tsx, 5c5034962 / 10ac91c2e). Editing triggers a build automatically (lib/admin/use-auto-build.ts; 385c22f85, 0140bbbac, bf5ffaa82) — there is no separate compile button. The wait for a build result runs in a generic long-poll worker (lib/long-poll/long-poll.worker.ts + use-long-poll.ts, 5ca6055aa) rather than a per-feature timer. A layout gear switches code / split / render by hiding a column with CSS, not by unmounting (EditorViewToggle.tsx). Renaming a slug is a first-class op from the editor header (RenamePage, owner/usecase/microsite.go:62, e1c14b27e). The same authoring set is exposed to the owner's AI client as microsite.write_file / get_draft / build / get_build / promote_to_staging / promote_to_live / rollback / delete / rename — the ops are declared per domain in owner/ops/microsites*.go and aggregated at the dispatcher (routes/dispatcher/collect.go:70), with microsite.guide returning the authoring guide (design system, widgets, how to show corpus inline). The header comment in owner/ops/microsites.go still says authoring is "deliberately MCP-only" — that predates the editor and is stale; both faces exist today.
A page owns its own store
A microsite can persist visitor input (a poll, a sign-up, a guestbook) without the owner adding a table. The store is NoSQL-shaped — opaque JSON (JavaScript Object Notation) documents in named collections — and physically isolated: each page gets its own Postgres schema (page_<id>, the capstore pattern), not a shared table filtered by id, so there is no WHERE clause to forget; DeletePage drops the schema (owner/usecase/microsite_store.go, a94909aa9). Public route: GET/POST /api/v1/pages/{slug}/store (routes/public/microsite_store.go:40). Reads are open; visitor writes are off until the owner opens them (microsites.store_writable, default false — "model C"; MCP op microsite.set_store_writable, eb2cf70ce). Bounds: 500 documents per page, 8 KiB per document, per-IP rate limit at the route. In the SDK the hook is useMicrositeStore(collection) (sdk/packages/react/src/use-microsite-store.ts:26; it was usePageStore in 5ec7263aa and renamed with the surface); the client base is /api/v1/pages (sdk/packages/core/src/client.ts:172). Owner-side inspection and cleanup are microsite.store_docs / store_delete_doc / store_clear.
What a page is made of: SDK widgets
AgentWidget— the visitor chat, adopting whatever session the code issued;GateWidget— code entry;PageNavWidget— links to the owner's other live pages (GET /api/v1/microsites,microsites.go:81).CorpusWidget— corpus cards, with a small query language:query="path:math/** sort:title limit:5"(subtree · sort recent or title · cap;querywins over a barelimit;sdk/packages/react/src/widgets/CorpusWidget.tsx:7, 113ba6a1a). Reads go through the same access-control as everything else.AssetWidget— a pool asset served same-origin at/api/v1/assets/{id}, gated to assets the microsite references; the builder's completion callback recomputes the page's asset references (RebuildMicrositeAssetRefs,corpus/usecase/note_asset_refs.go:138, called fromroutes/sys/builds.go:191; 836af0fea), so a referenced asset cannot be deleted from the pool.- Widgets own their vertical layout inline rather than through a Tailwind class the consumer may never compile (00a3bf9e4); the builder template declares
@source "../node_modules/@standmeet/sdk"so widget classes do compile (builder/template/src/theme.css:15, 6faa580f8), and the release build stripsdata-testidfrom the widgets (sdk/packages/react/tsup.config.ts, bec5394fb). The SDK is vendored into the builder image, not npm-installed (builder/Dockerfile:21).
SEO follows the page, not the instance
Per-page seo_title / seo_description / seo_image (schema.sql:746; migrations 2026-09-06-microsite-seo.sql, 2026-09-06-microsite-seo-image.sql) are injected into the served head as <title>, <meta name="description">, Open Graph (OG) and Twitter Card tags (routes/public/microsite_seo_head.go; 79a276cce, b19d8db39). Null means nothing is injected and the build keeps its own title. Live non-home microsites are listed in the dynamic sitemap under /p/ (routes/public/seo.go:107 → IndexedMicrosites, owner/usecase/seo_microsites.go:15; 701bf1648). The global SEO settings page and its seo_settings table were removed the same week (7037a434e, migration 2026-09-06-drop-seo-settings.sql) — SEO (search engine optimization) has no instance-wide home any more. MCP op: microsite.set_seo.
A code opens a page
access_codes.microsite_id (schema.sql:344, FK at line 758, migration 2026-08-23-code-page-binding.sql) binds a code to one page. The session response carries microsite_slug (routes/public/sessions.go:62) and the app lands the visitor on /p/<slug> (landOnRendering, app/src/lib/gate/use-absorb-code.ts:71), where the page's AgentWidget adopts the session. A page is a rendering of the code, not a change to it — same grant, same role, same quota, same accounting (microsite-is-the-codes-rendering.spec.ts). /c/<slug> is the code's stable landing path after redemption (c6c54ce88); the slug is a locator, never a credential (app/src/app/c/[slug]/page.tsx). microsites.allow_byoai (same migration) decides whether a page lets a reader bring their own key when no grant is shown — a per-page pill in the editor (afb7ee478) and microsite.set_byoai over MCP; the code side is codes.set_microsite.
Honest ceiling
- Single-owner serve.
listLive, the store, and the homepage resolve the sole owner; the URL carries no handle. Multi-tenant serving needs a handle in the path, not a redesign. - Build is a black box to the panel. The editor sees status and
error_message; there is no per-file diagnostic mapping, so a Tailwind or type error reads as one build failure. - The store is bounded, not indexed. 500 documents and JSONB (binary JSON) containment filters; a page that wants ordering, joins, or counters beyond that has outgrown the store and should become a connector.
Built 2026-08-23 → 2026-09-07. Tables microsites (schema.sql:728) and microsite_builds (schema.sql:790); serve in routes/public/microsites.go / microsite_serve.go / microsite_preview.go / microsite_seo_head.go / microsite_store.go; usecases in owner/usecase/microsite*.go; 18 microsite.* ops through the dispatcher (verified in e2e/test/norm-outward-toolset.spec.ts; paritymanifest.micrositeEntries() is deliberately empty because reach lives on each op). Tests: microsite.spec.ts (create → build → staging → live → rollback), microsite-rename, microsite-preview-before-publish, microsite-preview-follows-the-agent, microsite-store-admin + microsite-store-isolation (the security scenarios), microsite-per-page-seo, microsite-code-binding + microsite-is-the-codes-rendering, microsite-design-system, microsite-asset-widget, microsite-starter-actually-chats, microsites-linked-on-public-surfaces, the editor set (microsite-admin-ui, microsite-admin-authoring, microsite-editor-entry, microsite-editor-live-follow, microsite-editor-view-toggle), and the homepage set (homepage-served-at-root, homepage-served-at-site-root, homepage-auto-goes-live-at-claim, homepage-view-live-links-to-root, owner-homepage-edit-entry, default-home-look, default-homepage-cards-expand-inline).
Origin: read from standmeet-new main at 36789537d (v0.1.31) on 2026-09-07; design docs under docs/design/ were treated as seeds and not cited.