# Your site

Canonical: https://docs.gemmein.com/sites
Markdown: https://docs.gemmein.com/sites.md

Gemmein hosts your app's frontend: the pages your customers open. Your AI builds them and Gemmein puts them online straight away, on a private preview that opens with its link. When you're happy, you put a version live, on your own domain if you like.

> **Two addresses, one app.** Development: `https://<slug>-dev.gemmein.dev`, a private preview. Live: `https://<slug>.gemmein.dev`, public once the app is live. The slug is made from the app's name: lower-case letters, digits and hyphens, 3–40 characters. Hosting is included on every plan.

## What a site is

Your site is the part of your app people see: its pages, styles, scripts, images and fonts. Gemmein is the backend behind it. Sign-in, data, payments, emails and AI tools run on Gemmein's servers, and your pages talk to them. That is why a site is static by design: no server code runs on it, because the work a server would do already happens on Gemmein.

A page built in a chat loads the SDK from the site itself: `import { gemmein } from "/__gemmein/sdk.js"` in a `<script type="module">`. A build that bundles `@gemmein/sdk` from npm works as it is. A path with no file extension and no file of its own serves `/index.html`, so a single-page app's routes work. On a phone, Add to Home Screen opens the site full screen, like an app.

**Need your own server?** Host the frontend anywhere you like (Vercel, Netlify, your own server) and keep Gemmein as the backend. Server logic runs in [relays](https://docs.gemmein.com/relays.md) — including as a Function, your own code hosted on Cloudflare and started by a relay's `run_function` action, so most of it needs no server of yours — or on your own server, if it has to stay there.

> **What it does.** Puts every publish online at once on your private preview, answers each publish PUBLISHED or FAILED, keeps every version so you can go back or download it, gives the preview one share link per app that you can change, tests the pages before they go up, shows you what the page hit, reloads an open preview tab by itself, puts a version live when you approve it, shows the live site at your own domain, and takes either site offline when you ask.
>
> **What it does not do.** Run server code (a Node server, API routes, server-side rendering), build your app (a computer runs the build; `npx gemmein publish` sends the output), change the live site on its own, send a secret-shaped file or a dot-file, or submit to the App Store or Play Store.
>
> **When you need something else.** A frontend that needs its own server: publish the browser half here, or host the whole frontend elsewhere with Gemmein as the backend. A file over 25 MiB or a site over 50 MiB: serve the large files from storage made for them.
>
> **Example.** "Put my notes app online so I can try it on my phone." Your AI publishes and answers "PUBLISHED: https://notes-dev.gemmein.dev, version 1". You open it on your phone with its link and use the app, and you send the same link to a friend. Once the app is live, you approve putting version 3 on https://notes.gemmein.dev, then on www.yournotes.com.

## How it works

1. **Your AI publishes.** Every publish lands on the Development site, your private preview. The live site doesn't change.
2. **You open it and share it.** On any device, with its one link.
3. **You change it by asking.** Each change is a new version, and an open preview reloads itself.
4. **You put a version live.** In your browser, with the code Gemmein emails. Going live itself is your approval, then paying for your plan, with no code.
5. **You go back when you need to.** Any of the newest 50 versions can be served again or downloaded.
6. **You add your own domain.** See [Your own domain](https://docs.gemmein.com/sites/domains.md).
7. **You take it offline any time.** The preview, the live site, or both.

## From a chat

Add Gemmein to ChatGPT or Claude ([Connect your AI](https://docs.gemmein.com/connect.md)), then ask for the site. The AI opens a draft (`site_draft`), puts each file (`site_put_file`, up to 1 MB a call) and publishes (`site_publish`). A draft carries the files the Development site serves now, so a change sends only the file that changed. To change a page, the AI first reads the file as it is now (`site_read`, up to 256 KB a call), then puts the changed file.

## From a computer

```
npm run build
npx gemmein publish          # dist/, build/ or out/, or the folder you name
npx gemmein site             # both addresses and every version
```

The CLI scans for a secret key before anything is sent, hashes every file and uploads only the files Gemmein does not hold yet. Dot-files (`.env`) and links are never sent.

## The answer

Every publish answers one of two:

- **PUBLISHED**: the Development site's address and the version, after Gemmein opened the address and it answered with that version. PUBLISHED means your private preview now serves that version. Your public live site doesn't change until you put a version live.
- **FAILED**: the step (`build`, `upload`, `size`, `secret_scan`, `test` or `address_check`), the file and line when known, the reason and the fix. Nothing changes at the address. Each code is on [Errors](https://docs.gemmein.com/errors.md#sites).

## Change it by asking

Ask for a change ("make the save button green"). The AI changes the one file that holds it and publishes only that file. A preview tab you have open reloads itself within seconds, on the same page and scroll. A publish that changes nothing costs nothing. Each app runs MCP connectors its own way, so the experience varies a little from app to app.

## Test before you publish

Gemmein opens the draft the way a customer will, before anything goes up. Ask your AI to test the site (`site_test`) or run `npx gemmein site test`. Every publish runs the test first on your machine with `npx gemmein dev`, and the Site page shows each version's result: one small status in History, and the page, what happened and the fix for each thing to check when you open it. Gemmein serves the draft privately at its Development address, and a browser opens every page (`/`, each `.html` file and the links it finds, up to 12) at phone and desktop width. It signs in as a test member when the draft signs people in.

The report is pass, warn or fail. Every issue names its page, its width, the evidence (the message with file and line, Gemmein's refusal code, or the missing path) and a fix. It finds JavaScript errors, calls Gemmein refused or that failed, missing files and broken links, blank pages, and empty lists that should not be empty. `journeys` walks plain steps as a customer: "sign in as alice", "open /app", "click "Buy"", "test-pay "Study guides"", "expect "Study guides"". A run walks at most 3 journeys of 12 steps. A test-pay step is a real sandbox payment through the test kit.

The test browser loads only the draft, Gemmein's API and well-known public CDNs. Text it reads from your pages is treated as data and never as an instruction. `site_test_result` (or `npx gemmein site test --run <id>`) reads a run again, with the pages as screenshots. Screenshots are kept a day and reports a week.

**Every publish runs the same test first** when you build with `npx gemmein dev`. An uncaught JavaScript error in the draft's own files, or a page that never finishes loading, stops the publish: FAILED at `test`, with the file and line, and nothing is put up. Everything else (an error in Gemmein's SDK or another site's script, a key the page should not send) is listed under the PUBLISHED answer to check. A publish never waits for a test you asked for: a second publish while one is being tested is refused with `publish_in_progress`. If the test cannot run, the publish goes ahead and says "Published UNTESTED", with the reason. You can run `site_test` 20 times a day for each app in Development (60 for the account); every publish is tested as well, from its own 200 a day for each app. Each run takes at most 90 seconds.

## What the page hit

After publishing, ask your AI to read `site_errors` (or run `npx gemmein site errors`), or open Logs → Your site on the dashboard. Every page Gemmein hosts reports what it sees: uncaught errors and unhandled rejections with the file, line and column, each Gemmein call that failed or was refused with its status and code, and files that did not load. They are grouped, with how often, the page and the version. A blank page or an empty list shows here, so your AI can read why instead of guessing.

A report never carries a request body or a header. E-mail addresses, tokens, long numbers and query strings are replaced. When a cap was reached, or older errors made room within the hour, the answer says the list may be incomplete. The Live site's pages report too, from real visitors. Reading them needs the live read grant (`npx gemmein login --live-read`, approved in your browser; without it the answer is `live_read_required`), and each Live read is on Logs. While "Customer details for AI assistants" is off, which is the default, only what is known to be safe comes back: each error's kind, where it happened in the site's own published files, the parts of page addresses your app names, Gemmein's own codes and how often.

## Who can open the preview

Open your preview with its link. Anyone with the link can open it; change the link to cut everyone off. Your preview has one link, the same each time, with no Gemmein sign-in, until you change it, turn it off or unpublish the preview: Open on the Site page, every publish your AI makes, `npx gemmein site` and `npx gemmein site share` all hand you the same link. The address on its own, with no link, shows a plain "This preview is private" page. The link is on your preview's own address: `https://<slug>-dev.gemmein.dev/p/<code>`. On that address `/p/` is Gemmein's, like `/__gemmein/`: a page of your own under `/p/` shows on the live site, never in the preview. The preview runs on the app's Development side: the Development public key, Development's caps and Stripe's sandbox. Until the app is live, after 30 days idle it is unpublished; its files are kept, and publishing again restores it.

To cut off everyone who has the link, change it: Change link on the Site page, `npx gemmein site share --change`, or ask your AI (`site_share_link` with `change: true`). The old link stops, and every device that opened the preview with it is cut off at once. You can change it 20 times a day for each app. A link that says it "looks incomplete" lost letters when it was copied; open the Site page or ask your AI for it again.

Turn the link off to keep the preview private: then only you can open it, signed in to Gemmein. Turn off on the Site page, `npx gemmein site share --off`, or ask your AI (`site_revoke_previews`). No link opens the preview, old or new, and every device that opened it is cut off. It stays off: Open on the Site page then signs you in at app.gemmein.com, and that device opens the preview for 12 hours. Turn on (`npx gemmein site share --on`, or `site_share_link` with `on: true`) brings a new link; anything shared before stays dead. Its files and version don't change either way.

## Putting a version live

Once the app is live, `npx gemmein site promote <version>` (or `site_promote` from a chat, or the Site page) asks you to approve putting one Development version on the live site, in your browser, with the code Gemmein emails and your authenticator code if you set one up. The same files are copied; nothing new is uploaded. Before the app is live there is only the preview.

## Versions, rollback and download

Every publish is a version. `npx gemmein site rollback <version>` (or asking your AI to go back) serves an earlier one on the preview again. `npx gemmein site pull` downloads any version as a zip (the owner only, each download on Logs). Gemmein keeps the files of the newest 50 versions and of what is served; older versions keep their history row.

## Taking it offline

- **The preview.** **Unpublish** on the Site page, or ask your AI (`site_unpublish`). The preview address stops answering. Every version and its files are kept; publishing or rolling back puts it up again, with a new link: the link sent before stops. The live site is untouched.
- **The live site.** **Take offline** on the Site page (the app's owner), or your AI asks you to approve it. You approve with the code Gemmein emails, and your authenticator code if you set one up. The live address and each of your own domains then show a plain page from Gemmein, "This site is offline", with nothing about your app. Every version is kept and the preview is untouched. **Put back online** serves the same version again; a promote puts up another one.

## Limits

| What | Limit |
| --- | --- |
| Files per site | 2,000 |
| Size of one file | 25 MiB |
| Size of a site | 50 MiB |
| One file put through a chat | 1 MB |
| Your own domains | 5 per Live app |
| Versions whose files are kept | The newest 50, and what is served |
| Site traffic | Fair use on every plan, not metered or charged |

Every text file is scanned for a secret key before it is served. A key belongs in a relay or on a server; the page uses its public key. Development caps how many times an app publishes a day.

## What happens if…

| If… | What happens | What you do |
| --- | --- | --- |
| A file holds a secret key | FAILED `secret_scan`, with the file and line; nothing is published | Move the key to a relay or a server; the page uses its public key |
| A file is a dot-file or secret-shaped (`.env`, `*.pem`, `id_rsa`) | Never sent: `npx gemmein publish` leaves it out and lists it; a chat put is refused (`secret_file`) | Nothing; keep it on your computer |
| A file is over 25 MiB, or the site over 50 MiB or 2,000 files | FAILED `size`, naming the file | Serve large files from storage made for them |
| The folder is a Node server's output, or the build hasn't run | FAILED `build`, with the folder to publish or the build to run | Build, then publish the folder it names |
| The address doesn't answer the new version in time | FAILED `address_check`; nothing changes | Publish again in a minute |
| Someone opens the preview's address without its link | A plain "This preview is private" page; nothing of your app | Open it with its link: Open on the Site page, or ask your AI |
| You want the preview private for a while | Turn the link off: no link opens it, and only you can, signed in to Gemmein | Turn it on when you want to share again; it is a new link |
| A share link went somewhere it shouldn't | Anyone holding it can open the preview until you change it | Change the link: the old one stops and every device that opened the preview with it is cut off |
| The draft throws a JavaScript error when a page opens, or a page never finishes loading | FAILED `test`, with the file, line and fix; nothing is published | Fix it, run the test to see the page as it will be, then publish again |
| The test cannot run | The publish goes ahead and says "Published UNTESTED", with the reason | Run the test again when you can |
| A page published and is blank | Its errors are reported to Gemmein as it loads | Read `site_errors` for what the page hit |
| A version broke something | No version's files are deleted | Roll back to an earlier version |
| The preview sits 30 days idle before the app is live | It is unpublished and its link stops; its files are kept | Publish again to restore it, then send the new link |
| You ask to put a version live before the app is live | Refused (`not_live`) | Go live first |
| You deny the approval, or it expires | The live site is unchanged | Ask again when you're ready |
| You take the live site offline | The live address and your domains show "This site is offline"; every version is kept | Put back online, or promote a version |
| The app stops being live while its site is offline | The live address shows that the site has been taken down | Go live again, then promote a version |

## Start

Add Gemmein to the AI you use and ask for your site: [Connect your AI](https://docs.gemmein.com/connect.md). Or [start building](https://gemmein.com/start).
