# Cloudflare

> Deploy via Cloudflare Workers with static assets — the setup htmlpress.dev itself uses.

Cloudflare Workers can serve a directory of static assets directly: no separate compute, no SPA shim. This is how `htmlpress.dev` ships, and the same `wrangler.jsonc` shape works for any htmlpress site. Workers static assets also read the `_headers` file every build writes.

## Build the site

```sh
htmlpress build
```

Output lands in `.htmlpress/build/`.

## Configure `wrangler.jsonc`

Drop this at your project root:

```
{
  "name": "my-docs",
  "compatibility_date": "2026-04-01",
  "workers_dev": true,
  "assets": {
    "directory": ".htmlpress/build",
    "not_found_handling": "404-page",
  },
}
```

Three fields do the heavy lifting:

| Field                       | Purpose                                                                    |
| --------------------------- | -------------------------------------------------------------------------- |
| `assets.directory`          | Points Wrangler at the htmlpress build output. Every file is served as-is. |
| `assets.not_found_handling` | Answers unknown paths with the build's `404.html`.                         |
| `workers_dev`               | Gives you a `<name>.<account>.workers.dev` preview URL while testing.      |

## Deploy from your machine

```sh
htmlpress build
npx wrangler deploy
```

The first run prompts for `wrangler login` to authorize against your Cloudflare account.

## Deploy with GitHub Actions

```yaml
name: deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
        with:
          node-version: 24
      - run: npx gethtmlpress build
      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
```

Create a scoped API token at **My Profile → API Tokens** (the "Edit Cloudflare Workers" template grants exactly what you need) and store both values as repo secrets.

Without a workflow, Workers Builds can run the build itself: connect the repository in the dashboard and add a `build` block to `wrangler.jsonc`, as htmlpress.dev does with `"build": { "command": "pnpm build:docs" }`.

## Custom domain

Add a `routes` block once your zone is on Cloudflare:

```
{
  "name": "my-docs",
  "compatibility_date": "2026-04-01",
  "assets": { "directory": ".htmlpress/build", "not_found_handling": "404-page" },
  "routes": [{ "pattern": "docs.example.com", "custom_domain": true }],
}
```

`custom_domain: true` tells Cloudflare to provision a managed domain: DNS, certificate, and route are all handled for you. Set `site` to the same domain in `htmlpress.yaml` so absolute URLs in the sitemap line up.

> **Tip**
>
> The htmlpress repo's own `wrangler.jsonc` is a working reference; see the [project root](https://github.com/datisthq/htmlpress/blob/main/wrangler.jsonc).

## Markdown for agents

AI agents ask for a page as Markdown by sending `Accept: text/markdown`. Static assets always answer with HTML, so add a URL Rewrite Rule to your zone that points those requests at the page's [Markdown copy](/customization/ai-agents/): `/guides/build/` has `/guides/build/index.md` beside its HTML. URL Rewrite Rules are available on every plan, including Free, and the site stays fully static.

In the dashboard, open your domain, go to **Rules → Overview → Create rule → URL Rewrite Rule**, and fill in:

| Field                      | Value                                                                  |
| -------------------------- | ---------------------------------------------------------------------- |
| Rule name                  | `Markdown for agents`                                                  |
| If incoming requests match | Custom filter expression (below)                                       |
| Path                       | Rewrite to → **Dynamic** → `concat(http.request.uri.path, "index.md")` |
| Query                      | Preserve                                                               |

```
any(http.request.headers["accept"][*] contains "text/markdown") and ends_with(http.request.uri.path, "/")
```

Page URLs end in `/`, so the rule only touches pages. Static assets serve `.md` files as `text/markdown`, so the response type needs no extra rule.

> **Warning**
>
> Deploy a build that contains the Markdown copies before enabling the rule. Otherwise an agent asking for Markdown gets a 404.

## See also

- [AI Agents](/customization/ai-agents/): what the build publishes for agents.
- [Site Metadata](/customization/site-metadata/): `site` powers absolute URLs and the sitemap.
- [Deployment](/deployment/): back to the host index.
