# Recipes: static sites

Ready-to-copy examples, from a brand-new site to automatic deploys. Each
recipe has the command, what it prints, and a prompt to ask your agent for
the same thing. The output below is real, taken from rapadura in production.

Before you start: the CLI is installed and `rapadura login` is done
([Getting started](getting-started.md)). The examples use a site called
`php-sobral-2026`; use your own name.

## A new site in plain HTML

A folder with `index.html` at its root is already a site. Nothing to
install, nothing to configure.

```
site/
  index.html
  programacao.html
  palestrantes/index.html
  404.html
  style.css
  img/palco.png
```

```sh
rapadura publish ./site --site php-sobral-2026 --create --title "PHP Sobral 2026"
```

```text
Packed 7 files (0.0 MB), uploading...
Created php-sobral-2026.rapadura.net
PHP Sobral 2026 is live at https://php-sobral-2026.rapadura.net (version 1).
  live: https://php-sobral-2026.rapadura.net
  version: 1 (7 files, 0.0 MB)
  version url: https://php-sobral-2026--v1.rapadura.net
  next: Open https://php-sobral-2026.rapadura.net to confirm the result; the first HTTPS visit may take a few seconds while the certificate is issued.
  next: Publish again to the same site to update it. Roll back any time with: rapadura rollback php-sobral-2026.rapadura.net 1
  next: Version 1 keeps its own address for as long as it is retained: https://php-sobral-2026--v1.rapadura.net
```

What the server does with that folder:

- `programacao.html` answers at `/programacao` and at `/programacao.html`.
- `palestrantes/index.html` answers at `/palestrantes/` (and `/palestrantes`
  redirects there).
- `404.html` becomes the "not found" page.
- Images, CSS and JS are cached for a week. When you change one, change its
  name too (`style.v2.css`) if everyone must see it right away.

```prompt
Build a one-page hotsite for PHP Sobral 2026 (November 14, Sobral, Ceará) in ./site: intro, schedule, speakers and a sign-up button pointing at https://www.sympla.com.br/. Plain HTML and CSS, no frameworks. Then publish it on rapadura as php-sobral-2026 and tell me the address and the version.
```

## With a site generator

Run the build and publish **the output directory**, never the project root.

| tool | build | publish |
|---|---|---|
| Astro | `npm run build` | `./dist` |
| Vite (React, Vue, Svelte) | `npm run build` | `./dist` |
| Hugo | `hugo` | `./public` |
| Eleventy | `npx @11ty/eleventy` | `./_site` |
| Jekyll | `bundle exec jekyll build` | `./_site` |
| Next.js with `output: 'export'` | `npm run build` | `./out` |

```sh
npm run build && rapadura publish ./dist --site php-sobral-2026 --create
```

The site is served at the root of its hostname. If the generator asks for
the final address (`site` in Astro, `baseURL` in Hugo, `url` in Jekyll), use
`https://php-sobral-2026.rapadura.net/` and leave `base` empty.

**The usual stumble.** Publishing the repository root drags along `.env`,
keys or `.php` files, and rapadura refuses all of it at once, with the list:

```text
error (archive_forbidden_file): 2 file(s) cannot be published:
  .env (credential-like file)
  contato.php (server-side code is never executed on the static lane)
hint: publish a generated output directory that holds only the public files
```

There is no way to force it. Publish the output directory or remove the
files. `.git`, `node_modules`, `vendor` and `.cache` are skipped silently.

```prompt
Build this project and publish the output directory on rapadura as php-sobral-2026, creating the site if it does not exist yet. If rapadura refuses any file, show me the list and do not try to work around it.
```

## Update, check, roll back

Publishing again to the same site makes a new version. `-m` stores a note.

```sh
rapadura publish ./site --site php-sobral-2026 -m "registration open"
rapadura versions php-sobral-2026
```

```text
VERSION  STATUS    FILES  SIZE   BY    WHEN              URL                                       MESSAGE
v2       live      7      0.0MB  rtio  2026-10-13 11:54  https://php-sobral-2026--v2.rapadura.net  registration open
v1       archived  7      0.0MB  rtio  2026-10-13 11:54  https://php-sobral-2026--v1.rapadura.net
```

Every version keeps its own address (`php-sobral-2026--v1.rapadura.net`)
while it is retained: compare two versions side by side, or show someone a
version that is no longer live (after a rollback, say).

Something broke? Go back to the previous version. Nothing is rebuilt: the
switch is instant and atomic.

```sh
rapadura rollback php-sobral-2026 1
rapadura sites show php-sobral-2026
```

```text
php-sobral-2026.rapadura.net
  status: live
  version: v1
  owner: rtio
  live: https://php-sobral-2026.rapadura.net
  created: 2026-10-13 11:54
  title: PHP Sobral 2026
```

To move forward again, run `rollback` with the newer version's number. The
last 20 versions are kept.

```prompt
The last publish of php-sobral-2026 broke the home page. Look at its versions on rapadura, roll back to the previous one and confirm by opening the site.
```

## Redirects and the 404 page

A `_redirects` file at the site root, one rule per line:
`source target [301|302]`. Exact paths only, no wildcards.

```
/inscricoes   https://www.sympla.com.br/   302
/2025/        /                            301
```

```sh
curl -sI https://php-sobral-2026.rapadura.net/inscricoes | grep -iE '^(HTTP|location)'
```

```text
HTTP/2 302
location: https://www.sympla.com.br/
```

`_redirects` itself is never served, and an invalid line refuses the publish
saying which one. For the error page, a `404.html` at the root is enough.

## Take a site offline and bring it back

```sh
rapadura sites delete php-sobral-2026 --yes
```

```text
php-sobral-2026.rapadura.net is offline. Files are kept for a while; bring it back with: rapadura sites restore php-sobral-2026.rapadura.net
```

Visitors get `410 Gone` and the name stays reserved for you. Changed your
mind?

```sh
rapadura sites restore php-sobral-2026
```

```text
php-sobral-2026.rapadura.net is back online at https://php-sobral-2026.rapadura.net (status live, version v1).
```

The site comes back with the version that was live and all its history.

## Your own domain

A static hotsite can also answer at a domain the event owns, like
`www.phpsobral.com.br`. Ask for it; the request waits for the owner's
approval and tells you which DNS record to create meanwhile:

```sh
rapadura domain add php-sobral-2026 www.phpsobral.com.br --reason "event domain, registered by the organizers"
```

```text
Request #2 (domain.add www.phpsobral.com.br → php-sobral-2026.rapadura.net) is waiting for the owner's approval.
  next: Point www.phpsobral.com.br at 168.196.105.251 (A record) — the certificate is issued automatically on first visit after approval.
  next: Nothing changes until the owner approves; the site keeps answering at https://php-sobral-2026.rapadura.net.
  next: The owner decides at https://agents.rapadura.net/approvals or with: rapadura approvals approve 2
  next: Undecided, the request expires on 2026-10-20. Follow it with: rapadura approvals
```

Follow it with `rapadura approvals`; once approved, `rapadura domain list
php-sobral-2026` shows the domain. `rapadura domain remove` detaches it with
no approval. The rules (which names are refused, WordPress sites) are in
[Rules and limits](rules.md#names).

```prompt
Ask rapadura to map www.phpsobral.com.br to the php-sobral-2026 hotsite, tell me exactly which DNS record I must create and how I can follow the request.
```

## On the phpcomrapadura.org domain

The same site can live under `phpcomrapadura.org`. Use the full name:

```sh
rapadura publish ./site --site php-sobral-2026.phpcomrapadura.org --create
```

or `--site php-sobral-2026 --zone phpcomrapadura.org`. They are two
independent sites, each with its own versions. `rapadura zones` lists the
available domains and the limits.

## Watch the traffic

```sh
rapadura logs php-sobral-2026 -n 20
```

Each line is JSON with time, path, status, size and browser. With `jq`, the
most viewed pages among the last 2000 requests:

```sh
rapadura --json logs php-sobral-2026 -n 2000 \
  | jq -r '.lines[] | fromjson | .uri' | sort | uniq -c | sort -rn | head
```

```prompt
Look at the last 2000 requests to php-sobral-2026 on rapadura and tell me the most visited pages, how many 404s there were and for which paths.
```

## Publish on every push (GitHub Actions)

Ask the administrator for a token just for CI, one per repository (they
mint it with `rapadura admin bootstrap-token`, see
[Administration](admin.md#tokens)). Save it as the `RAPADURA_TOKEN` secret
in the repository. Never reuse your computer's token and never put a token
in a project file.

```yaml
# .github/workflows/publish.yml
name: publish
on:
  push:
    branches: [main]

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci && npm run build
      - name: Install the rapadura CLI
        run: |
          curl -fsSL https://agents.rapadura.net/install.sh | sh
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
      - name: Publish
        run: rapadura publish ./dist --site php-sobral-2026 -m "commit ${GITHUB_SHA::7}"
        env:
          RAPADURA_TOKEN: ${{ secrets.RAPADURA_TOKEN }}
```

If the publish fails, the step fails and the previous version stays live.

## More prompts to try

```prompt
List my hotsites on rapadura with each one's version and tell me which have not been published to in more than 30 days.
```

```prompt
Update only the schedule of php-sobral-2026 from programacao.csv (columns: time, title, speaker). Keep the rest of the site as is, publish with the message "schedule updated" and send me the address.
```

```prompt
Copy php-sobral-2026 to php-sobral-2026.phpcomrapadura.org: publish the same folder there and check that both URLs open.
```
