# Cloudflare Pages — frontend project configuration

## Configuration files

Every frontend project deployed on Cloudflare Pages must have these files in `public/`:

```
public/
├── _headers        # HTTP cache headers
├── _routes.json    # SPA routing
├── favicon.ico
└── img/
```

Plus at the project root:

```
wrangler.toml       # env vars and project config
```

---

## 1. wrangler.toml

**Purpose**: env vars and project configuration.

**IMPORTANT**: when this file exists, Cloudflare blocks variable edits from the dashboard. The file is the source of truth.

```toml
name = "project-name"
compatibility_date = "2024-12-01"
pages_build_output_dir = "dist"
compatibility_flags = ["nodejs_compat"]

# Production variables
[vars]
VITE_API_URL = "https://api.cryptonite.trade"
VITE_NODE_ENV = "production"
# ... more VITE_* variables

# Preview variables (PR deployments)
[env.preview.vars]
VITE_API_URL = "https://api.cryptonite.trade"
VITE_NODE_ENV = "preview"
```

### Notes

- `VITE_`-prefixed variables are injected at build time
- `pages_build_output_dir` must match the Vite/Vinxi output
- `compatibility_flags = ["nodejs_compat"]` is required for SolidStart/Vinxi

---

## 2. \_headers

**Purpose**: HTTP cache control to avoid stale JS files.

**Location**: `public/_headers`

```
# Cache headers for Cloudflare Pages
# During active development, use a short cache to avoid stale JS

# Assets with hashed filenames — moderate cache + immutable
/_build/assets/*
  Cache-Control: public, max-age=3600, immutable

# HTML — no cache so users always get the latest
/*.html
  Cache-Control: no-cache, no-store, must-revalidate

# Root index — no cache
/
  Cache-Control: no-cache, no-store, must-revalidate

# Images — long cache (1 day)
/img/*
  Cache-Control: public, max-age=86400

# Favicon and manifest
/favicon.ico
  Cache-Control: public, max-age=86400

/manifest.json
  Cache-Control: public, max-age=3600
```

### Why it matters

**Problem**: with frequent deploys, JS files change hash (e.g. `index-abc123.js` → `index-def456.js`). If the browser cached the old HTML, it asks for a JS file that no longer exists → blank screen.

**Fix**:

- HTML uncached → always pulls the current JS references
- JS short-cache + immutable → cached but invalidates fast

### Recommended values

| Phase               | JS max-age       | Reason                            |
| ------------------- | ---------------- | --------------------------------- |
| Active development  | 3600 (1 h)       | Frequent deploys                  |
| Stable production   | 31536000 (1 yr)  | Hashed filenames are immutable    |

---

## 3. \_routes.json

**Purpose**: configure the SPA fallback and exclude static assets.

**Location**: `public/_routes.json`

```json
{
  "version": 1,
  "include": ["/*"],
  "exclude": ["/_build/*", "/img/*", "/favicon.ico", "/manifest.json"]
}
```

### Explanation

- `include: ["/*"]` → every route goes through the server (SPA routing)
- `exclude` → these paths are served as static files directly

**CRITICAL**: without `exclude: ["/_build/*"]`, JS files would return `index.html` when missing, producing the "Expected JavaScript but got text/html" error.

---

## Projects using this configuration

### Cryptonite main stack (SolidStart/Vinxi)

| Project  | wrangler.toml | \_headers | \_routes.json | Build output       |
| -------- | ------------- | --------- | ------------- | ------------------ |
| broker   | ✅            | ✅        | ✅            | `/_build/assets/*` |
| webapp   | ✅            | ✅        | ✅            | `/_build/assets/*` |
| master   | ✅            | ✅        | ✅            | `/_build/assets/*` |

### Other projects (standard Vite)

| Project               | wrangler.toml | \_headers | \_routes.json | Build output |
| --------------------- | ------------- | --------- | ------------- | ------------ |
| psmailer/webapp       | ✅            | ✅        | ✅            | `/assets/*`  |
| gpt/mediabuyer/webapp | ✅            | ✅        | ✅            | `/assets/*`  |

**Note**: SolidStart/Vinxi projects emit assets to `/_build/assets/*`, standard Vite uses `/assets/*`. Adjust `_headers` and `_routes.json` accordingly.

---

## Checklist for a new frontend project

- [ ] Create `wrangler.toml` with the env vars
- [ ] Create `public/_headers` with cache control
- [ ] Create `public/_routes.json` with the asset exclusions
- [ ] Verify `pages_build_output_dir` matches the build output
- [ ] Test the preview deploy before production

---

## Troubleshooting

### Error: "Expected JavaScript but got text/html"

**Cause**: the JS file doesn't exist; Cloudflare returns index.html as a fallback.
**Fix**:

1. User side: hard refresh (`Cmd+Shift+R`)
2. Project side: verify `_routes.json` excludes `/_build/*`
3. Project side: add `_headers` with a short cache for HTML

### Variables aren't applied at build time

**Cause**: `wrangler.toml` isn't at the root, or its syntax is wrong.
**Fix**: verify the location and the TOML syntax.

### Cloudflare dashboard won't let me edit variables

**Cause**: `wrangler.toml` exists — this is expected behavior.
**Fix**: edit variables in `wrangler.toml` and redeploy.
