# Go live

> From a checkout that runs locally to a site on your own domain — pnpm cf:setup does the Cloudflare side in one command, then a short list of things only you can do.

You are reading one page of the ZeroDirs documentation. ZeroDirs is a paid Astro + Cloudflare Workers starter for directory sites: every page is rendered to static HTML at build time, the site ships no client JavaScript outside `/search/`, and one file — `site.config.ts` — carries roughly 80% of the customisation.

Two things to hold on to before you act on anything below:

- `site.config.ts` is validated by a zod schema with ten cross-field rules. After any edit to it, run `pnpm check:config`; every problem is reported as `site.config.ts › <path>: <message>` and the whole file is checked at once.
- The repository ships its own `AGENTS.md` with twenty hard rules, and a machine-checked test suite behind them. If you are working inside a ZeroDirs project, read that file first — it overrides anything general you infer from this page.

Source: https://zerodirs.com/docs/deploy/go-live/

---

A ZeroDirs site is static files plus a small Worker. Going live is one command for the
mechanical part and a short list for the parts that need your account. When this page is done
you have a site on your domain, a database behind `/submit/` and `/admin/`, and a printed list of
what is still switched off.

> **Free edition**
>
> The free edition has no Worker, so there is nothing to set up: `pnpm build && pnpm exec wrangler deploy`
> publishes the static files, and [Deploy on git push](https://zerodirs.com/docs/deploy/git-push/) does it on every push.
> `pnpm cf:setup` is not in that edition.

## Before you start

Three things, each with a command that proves it:

| Check | Command | You should see |
| --- | --- | --- |
| wrangler is signed in to the right account | `pnpm exec wrangler whoami` | your account name and id. If it says you are not logged in: `pnpm exec wrangler login` opens a browser |
| `site.url` is your real domain | `pnpm check:config` | `site.config.ts OK — <your site name> (https://your-domain.com)`. `https://`, no trailing slash |
| the domain is on Cloudflare | Cloudflare dashboard → your account → the zone is listed | if it is not, add it now, or deploy to `*.workers.dev` first with `--workers-dev` and come back |

`site.url` matters more than it looks: canonicals, the sitemap, the feeds and the OG images bake
it in at build time. `pnpm cf:setup` refuses to run while it still says `demo.zerodirs.com` or
`example.com`.

## Setup

1. See what will happen. Nothing changes yet.

   ```sh title="Terminal"
   pnpm cf:setup --dry-run
   ```

   The plan names the Worker, the route, the D1 database and the R2 bucket it is about to
   create or reuse, then stops with `Nothing changed.`:

   ```text
   Plan:
     Worker name      acme-directory  (renamed from the template)
     Route            acme.com (custom domain)  (changes wrangler.jsonc)
     D1 database      acme-directory  (create)
     R2 bucket        acme-directory-media  (create if missing)
     Then             migrations → build → deploy → ADMIN_SECRET / TOKEN_SECRET

   Nothing changed.
   ```

2. Run it.

   ```sh title="Terminal"
   pnpm cf:setup
   ```

   It asks once, then does every step in order, printing a `✔` per step and skipping what is
   already done, so you can run it again later. In order: it renames the Worker after your site
   (only while it still carries the template's name), points `wrangler.jsonc` at `site.url` as a
   custom domain, creates the D1 database and writes its id into `wrangler.jsonc`, creates the R2
   bucket, leaves the `send_email` binding out when your account cannot send mail yet, regenerates
   `worker-configuration.d.ts`, applies the migrations to the production database, builds, deploys,
   and sets `ADMIN_SECRET` and `TOKEN_SECRET` on the Worker.

3. Store the admin password.

   If `.dev.vars` still held the placeholder secrets, they are replaced by generated ones, written
   back into `.dev.vars` so local and production agree, and the `/admin/` password is printed **once**:

   ```text
     Your /admin/ password (generated, also saved to .dev.vars — store it somewhere safe):

         <the value>
   ```

   Put it in your password manager now. It is also `ADMIN_SECRET` in `.dev.vars`, which is
   gitignored and stays on this machine.

4. Read "What is left".

   The command ends with the URL of the site and a list filtered to your configuration:

   ```text
   Done. The site is at https://acme.com

   What is left:
     • Approved submissions live in D1; the site you just deployed was built from src/content/listings. …
     • providers.rebuild is "deploy-hook": connect the repository to Workers Builds, create a Deploy Hook …
     • Paid plans are on: pnpm cf:secrets STRIPE_SECRET_KEY STRIPE_WEBHOOK_SECRET, …
     • Open /admin/login/ with the ADMIN_SECRET from .dev.vars, and /admin/ will list anything this configuration still needs.

   Run `pnpm cf:setup` again any time; every step above skips what is already done.
   ```

   Each line is one of the pages under [What is left](#what-is-left) below.

### Without a domain yet

```sh title="Terminal"
pnpm cf:setup --workers-dev
```

The site goes to `<worker-name>.<your-subdomain>.workers.dev` and `wrangler.jsonc` keeps no route.
When the domain is on Cloudflare, set `site.url` to it and run `pnpm cf:setup` again without the
flag: it rewrites the route and redeploys.

### If it stops

The message says why. The three usual reasons:

| Message | Fix |
| --- | --- |
| `site.url is still …` | Set `site.url` in `site.config.ts` to your domain, then run it again |
| R2 could not create the bucket | R2 needs a one-time sign-up in the dashboard (R2 → Get started). Do that, run it again |
| the domain does not answer after a few minutes | The zone is not on this account. Add the domain to Cloudflare, then `pnpm cf:setup` again |

## Verify

| | Command or URL | You should see |
| --- | --- | --- |
| The site | `https://<your domain>/` | your home page, your name in the footer |
| The admin | `https://<your domain>/admin/login/` | the login form accepts the password from step 3 |
| What is still needed | `https://<your domain>/admin/` | the configuration report at the top lists only what you deliberately left for later |
| What the Worker holds | `pnpm cf:secrets` | a list of the secrets the deployed Worker has and which ones your `site.config.ts` still needs |

## What is left

`cf:setup` deliberately stops at the edge of your account: it signs nobody in, creates no API
tokens and touches no dashboard. These are the steps it hands to you, in the order they pay off.

| Step | Why | Page |
| --- | --- | --- |
| Build the site from the database | Approved submissions live in D1, but the site you just deployed was built from `src/content/listings/` | [Listings from D1](https://zerodirs.com/docs/deploy/listings-from-d1/) |
| Deploy on every push, and after every approval | A Deploy Hook lets an approval trigger a build | [Deploy on git push](https://zerodirs.com/docs/deploy/git-push/), then [Publishing on approval](https://zerodirs.com/docs/submissions/publishing/) |
| Send mail | Receipts, approvals and your own notifications wait in the outbox until a provider can send | [Email](https://zerodirs.com/docs/submissions/email/) |
| Take money | Paid plans ship on and need two Stripe secrets and a webhook | [Plans and payments](https://zerodirs.com/docs/submissions/payments/) |
| Let submitters sign in with Google | Accounts work with emailed links already; Google is optional | [Submitter accounts](https://zerodirs.com/docs/submissions/accounts/) |

Every secret on that list goes in with `pnpm cf:secrets`, which prompts for one key at a time and
never prints or stores a value. [Environment and secrets](https://zerodirs.com/docs/deploy/environment/) is the full map.

## By hand

**What cf:setup replaces**

The same result without the script, for a copy where you would rather see each command:

```sh title="Terminal"
pnpm exec wrangler login
pnpm exec wrangler d1 create acme-directory        # paste the id into wrangler.jsonc → d1_databases[0].database_id
pnpm exec wrangler r2 bucket create acme-directory-media
pnpm cf:typegen                                     # worker-configuration.d.ts follows the bindings
pnpm db:migrate:remote
pnpm check:config
pnpm deploy                                         # = astro build && wrangler deploy
pnpm cf:secrets ADMIN_SECRET TOKEN_SECRET
```

Set `name` and `routes` in `wrangler.jsonc` yourself first. `cf:setup` is idempotent; running it
after a manual setup only fills what is missing.

**Static files only**

To publish the pages and nothing else — no database, no submissions — the deploy is:

```sh title="Terminal"
pnpm check:config
pnpm deploy
```

`/submit/`, `/admin/` and the checkout routes answer 404 until a database exists, and the rest of
the site is unaffected. This is also the whole deploy of the free edition.

## Files

- scripts/
  - cloudflare-setup.ts the command itself, step by step, with `--dry-run`, `--workers-dev` and `--yes`
  - dev-setup.ts what `pnpm dev` runs first: `.env` and `.dev.vars` from their examples, local migrations
- wrangler.jsonc `name`, `routes`, the D1 id and the R2 bucket are written here
- worker-configuration.d.ts regenerated from the bindings; `pnpm check` type-errors without it
- .dev.vars gitignored; `ADMIN_SECRET` and `TOKEN_SECRET` end up here as well as on the Worker

## Related

- [Environment and secrets](https://zerodirs.com/docs/deploy/environment/) — Every variable and secret, where it lives, and what breaks without it.
- [Deploy on git push](https://zerodirs.com/docs/deploy/git-push/) — Workers Builds or GitHub Actions — pick one.
- [Publishing on approval](https://zerodirs.com/docs/submissions/publishing/) — The Deploy Hook that turns an approval into a live page.
- [Troubleshooting](https://zerodirs.com/docs/run/troubleshooting/) — The build stopped, the deploy looks wrong, the canonicals point elsewhere.
