Skip to content

Page Generation

Page Generation is how the Builder turns your description into a real, multi-page App. You do not hand-write pages. You describe what you want, and the Builder plans every screen, route, and layout, then generates the components one by one until the App is live in the Preview.

Every App is generated from scratch for your specific prompt. There is no template, no scaffold, and no boilerplate — two Apps for two different descriptions never share starting files. This page explains the structure the Builder produces and the rules it follows, so you know what to expect and how to steer it.

When you agree on a plan, the Builder builds the App in four visible stages. You watch each stage land in the Preview iframe — first the empty shell, then dashed layout frames, then real components filling in one at a time.

StageWhat you seeWhat is happening
PlanningThe agreed plan, no App yetThe Builder provisions the App once
Provisioned”App is ready, no pages yet”The Builder writes the page layouts and the router
LayoutDashed region frames in the PreviewThe Builder generates components and APIs, one by one — each redeploys
ComponentsAll components rendered and liveThe build is complete

You do not drive these stages. The Builder advances through them on its own and shows progress in the Preview after each step.

The plan: pages, routes, layouts, components

Section titled “The plan: pages, routes, layouts, components”

Before generating anything, the Builder writes a plan — a formal description of your App as four things, planned in this order:

  1. The user journey — what a Visitor needs to accomplish.
  2. Pages — the screens that satisfy that journey.
  3. Routes — the URL paths that resolve to those pages.
  4. Components — the reusable pieces each page is built from.

This order is deliberate: the journey decides the pages, the pages decide the routes, and the routes decide the layout and components. The Builder favors the smallest plan that delivers what you asked for — typically a handful of pages and components — and a reviewer pass catches scope creep before generation starts. If your App comes back smaller than you imagined, that is the planning bias working as intended; ask for more and the Builder will refine the plan.

A page has a stable id, a title, a layout, and a list of components:

{
"id": "page-home",
"title": "Home",
"layout": {
"header": {
"type": "Stack",
"children": [{ "type": "custom", "sourceFile": "src/components/AppHeader.tsx" }]
},
"main": {
"type": "Stack",
"children": [{ "type": "custom", "sourceFile": "src/components/Hero.tsx" }]
}
},
"components": [
{
"id": "AppHeader",
"name": "AppHeader",
"description": "Top navigation bar; shows Sign in or Sign out based on session state.",
"kind": "layout"
}
]
}

Page ids are kebab-case and always start with page- (for example page-home, page-sign-in, page-post-detail). They are stable identifiers, not labels — the router, the layout files, and the component registry all key off them. Once a page id is set, it never changes during a conversation, even if you rename the page in conversation. Renaming an id mid-build is a structural break, not a cosmetic edit, so the Builder keeps it fixed.

Routes map URL paths to pages:

[
{ "path": "/", "pageId": "page-home" },
{ "path": "/post/:slug", "pageId": "page-post-detail" }
]

The rules the Builder enforces:

  • Exactly one route must use path: "/" — that is your homepage, and it must point at a real page.
  • Every route’s pageId must reference a page that actually exists in the plan.
  • Paths are Express-style: a static segment like /checkout, or a parameter like /post/:slug. There are no catch-all patterns in v1.

Every page lays out into four named regions, and only these four:

  • header
  • sidebar
  • main
  • footer

main is the only required region. A page with no header or sidebar is perfectly valid; a page with no main is rejected. Any region key other than these four fails validation, and layouts cannot be nested in v1.

Each region’s body is a tree of platform layout primitives, or a reference to one of your custom components — and you can mix both freely in the same region:

  • Layout primitives: Section, Stack, Card, Form, List, Image, Button, Input, Heading, Text.
  • Custom component reference: { "type": "custom", "sourceFile": "src/components/Hero.tsx" }.

Each component has a globally unique id. Uniqueness is enforced across all pages, not per page — that is what makes a component safe to share. When a component like AppHeader appears on several pages, it has one id and one source file, and every page references that same id. Two pages that each declare a nominally different but actually equivalent component is a plan error the Builder catches.

Component ids follow two conventions: layout components are kebab-case, and custom components are PascalCase.

Styling with Tailwind: write complete class names

Section titled “Styling with Tailwind: write complete class names”

Generated components style themselves with Tailwind utility classes. There is one hard rule, and it is a build-system property, not a style preference: a class string must be a complete literal. Tailwind’s CSS is compiled at build time by a scanner that reads your source as text — it cannot run JavaScript to discover what a dynamic expression evaluates to.

What this means in practice:

<!-- Works: the scanner sees the whole class name -->
<div class="bg-blue-500 text-white">
<!-- Broken: the scanner never sees "bg-red-500", so it is not compiled -->
<div class={"bg-" + color + "-500"}>
<div class={`text-${size}`}>

A dynamically built class name does not error — the component still renders. It just renders unstyled for that property, because the class never made it into the compiled CSS. The build emits a soft tailwind.dynamic_class warning when it spots an interpolated class string, but it does not fail the build. If a generated component looks unstyled, an interpolated class name is the first thing to check. To keep your App’s styling reliable, ask the Builder for variants as complete classes rather than computed ones.

If your App uses Identity (email + password sign-in), the Builder follows a fixed page topology so session state wires up correctly:

  • A dedicated sign-in page at /sign-in, containing the AuthSignIn component.
  • A dedicated sign-up page at /sign-up, containing the AuthSignUp component.
  • An AuthSignOut control embedded in the header of the homepage and other authenticated pages, shown only to signed-in Visitors.

The homepage must not contain the sign-in form — that is a platform invariant. Keeping the forms on their own well-known routes is what lets the Builder reliably connect session state into your header.

There is no drag-and-drop or visual editor in v1. You change a generated App the same way you built it: describe the change to the Builder in the conversation, and it regenerates the affected components. The plan’s page and component ids stay stable across edits, so the router and layout keep working while individual pieces are rewritten.

  • Nothing to set up. Page Generation is how every App is built — there is no provider account, no set_secret, and no plan flag. You consume it by describing your App to the Builder.

  • The plan is the contract. The Builder validates the plan before it generates a line of code: a missing homepage route, a route pointing at a page that does not exist, a duplicate component id, or a page with no main region all fail validation up front, before any component is generated.

  • Ids are forever (within a conversation). Page and component ids never change mid-build. If you want a page to feel different, change its title and contents — the id underneath stays put so nothing downstream breaks.

  • Smaller is the default. The Builder plans the minimum App that delivers your request. If you want more pages, more flows, or more components, say so and it will refine the plan rather than guess.

  • Secrets belong to other capabilities. Page Generation itself consumes none of your secrets. When a generated App uses auth, payments, messaging, or another Capability, the secret names that Capability needs are documented on its own page.