# {{projectName}} / {{appName}}

Static page **+** serverless email form (template: **frontend-contact**) —
the canonical "static frontend, serverless backend" combo.

- `src/pages/index.tsx` — a static page (`renderMode: 'static'`).
- `src/components/ContactForm.island.tsx` — the only hydrated part (an island).
- `functions/sendMessage.serverless.ts` — a standalone serverless function
  that sends mail via Resend. Deployed SEPARATELY from the page.

## Run it locally (two terminals)

```bash
pnpm install

# Terminal 1 — the static site (HMR dev server)
pnpm --filter @{{projectName}}/{{appName}} dev

# Terminal 2 — the serverless function on http://localhost:8910
pnpm --filter @{{projectName}}/{{appName}} fn:dev
```

The form POSTs to `http://localhost:8910` (see `src/config.ts`). The function
answers cross-origin because the framework's runner sends CORS headers — the
same headers Cloudflare / Scaleway add at the edge.

Without `RESEND_API_KEY` set, the function returns a clean `503
email-not-configured` and the form shows it — it never pretends to send. To
send for real:

```bash
RESEND_API_KEY=re_... CONTACT_TO=you@example.com \
  pnpm --filter @{{projectName}}/{{appName}} fn:dev
```

## Deploy

The page and the function ship to **different** places — that's the point.

```bash
# 1. The static page → a CDN
pnpm --filter @{{projectName}}/{{appName}} build
voltro static deploy --host cloudflare-pages --project-name {{appName}}

# 2. The function → a function host (scales to zero)
cd apps/{{projectName}}/{{appName}}
voltro serverless deploy --target scaleway --namespace-id <id>
#   ...or --target cloudflare, or --target node for a self-hosted sidecar
```

Then point the form at the deployed function: set `CONTACT_ENDPOINT` in
`src/config.ts` to the URL `voltro serverless deploy` printed, and rebuild.

## What this template demonstrates

| Concept | Where |
|---|---|
| Static page + selective hydration (`interactive: 'islands'`) | `src/pages/index.tsx` |
| An island with `hydrate: 'visible'` | `src/components/ContactForm.island.tsx` |
| A serverless function (`defineServerless`) | `functions/sendMessage.serverless.ts` |
| Schema-validated input → typed 400 | the function's `input` |
| Outbound HTTP via `HttpClient` (Resend) | the function's handler |
| `ServerlessHttpError` to control the status (503 / 502) | the function's handler |
| Secrets from `ctx.env` (RESEND_API_KEY) | the function's handler |
| Cross-origin fetch that works locally (CORS) | the runner + the island |
