> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stage.systems/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up a site

> Start from the client template, give it an org and the shared credentials, and get a green /admin locally.

## What you start from

<Note>
  This page is the **Astro** template, the default. A Next.js site uses the Next template and the `@sp-stage/next` adapter; see [Next.js sites](/developers/next/overview).
</Note>

The **client template** is a minimal Astro site that already meets the whole contract: the SDK initialised, the middleware re-exported, the `/admin` mount, a reference `Hero` component, the blog and forms renderers, the header and footer globals, the consent banner, and the analytics scripts in the base layout. Clone it and build the design out from there.

```
src/
├── components/
│   ├── global/     Header, Footer, Media, FormOverlay, ConsentBanner
│   ├── home/       Hero (the reference component)
│   ├── blog/       ArticleList, ArticleBody, Prose
│   └── forms/      the six question renderers, PageBreak, FormBody
├── layouts/Base.astro     SEO, analytics scripts, header and footer
├── lib/
│   ├── stage.ts           SDK init
│   ├── components.ts      component type → renderer
│   ├── stage-anchors.ts   the anchor helpers, re-exported from the SDK
│   └── admin-schema.ts    what the admin can add
├── pages/
│   ├── index.astro, [...slug].astro     public pages
│   ├── news/                            articles
│   ├── preview/                         the gated edit canvas
│   └── admin.astro                      the /admin mount
middleware.ts                            re-exports the SDK middleware
```

A standalone site installs `@sp-stage/sdk` and `@sp-stage/admin` from the private registry at an exact version. In the platform monorepo the template consumes them from source.

## Environment

| Variable                                                                             | What it is                                                                                                                                     |
| ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `STAGE_DB_URL`, `STAGE_PUBLIC_KEY`                                                   | The database URL and its public key. Shared across sites. Read through `lib/platform-env.ts`, which fails at first import if either is missing |
| `ORG_ID`                                                                             | This site's org. Every read and write is scoped by it                                                                                          |
| `SITE_URL`                                                                           | The canonical domain, `www` included if that is where the site lives. Campaign links are minted against the org's site URL                     |
| `API_URL`                                                                            | The shared API base, no trailing slash. Enables uploads, settings, publishing and requests from `/admin`                                       |
| `ADMIN_LOGO_URL`, `ADMIN_BRAND_NAME`, `ADMIN_LOGO_FONT`, `ADMIN_FONT_STYLESHEET_URL` | Optional. Brand the sign-in screen                                                                                                             |

Sites hold **no service-role key**. The public key plus row-level security covers every read a site makes; anything needing more goes through the API.

<Warning>
  `API_URL` must not end in a slash. The admin normalises it, but inline scripts in the base layout concatenate it raw, so a trailing slash produces `//api/...` requests.
</Warning>

## The org

An org is a row in `organizations` with a `settings` JSON. Two settings matter on day one:

* `qa_mode: true` turns inline editing on. Without it the bar reads that editing is off.
* `site_url` is the canonical `https://` address campaign links are minted against.

Members are rows in `memberships` (`user_id`, `org_id`, `role` of `editor` or `admin`). A person can belong to several orgs.

## The admin mount

`pages/admin.astro` is a static shell that boots the admin. It passes the site's schema and two switches:

```ts theme={null}
mountAdmin(el, {
  dbUrl, publicKey, orgId, apiUrl,
  schema: ADMIN_SCHEMA,      // what the admin can add: lib/admin-schema.ts
  previewBase: "/preview/",  // the edit canvas loads the gated SSR preview route
  editChrome: "notch",       // the layout-safe edit chrome
});
```

The public site stays static. The **preview route** is server-rendered and membership-gated; it is what the edit canvas loads so a save shows on the next reload. Its gate caches allow decisions briefly and never caches a denial.

## Local development

```bash theme={null}
pnpm install
pnpm dev        # site + /admin on :4321
pnpm build      # static build to dist/
```

Edge middleware does not run under the dev server, so the template ships a dev-only Astro middleware that applies the same campaign-landing and private-site verdicts. It is compiled out of static builds.

## Smoke test

1. Open `/admin`, sign in as a member of the org.
2. Press **Edit**, click the hero title, type, press **Publish**.
3. The bar reads **Live**. With no deploy hook set it reads **Failed: No deploy hook**, which is expected until the site has a rebuild address.

## Next steps

<CardGroup cols={2}>
  <Card title="Anchors" href="/developers/editing/anchors">
    The attributes that make a field editable.
  </Card>

  <Card title="Components" href="/developers/editing/components">
    Build and register a renderer.
  </Card>
</CardGroup>
