# CDN

Purge cached content at the edge with the noop or Cloudflare adapter.

`cdn.CDN` invalidates cached content at a CDN provider's edge so visitors see fresh files immediately. The `noop` adapter discards purges (the zero-infra default); the `cloudflare` adapter calls the Cloudflare cache-purge API.

## Quick start

Pick `noop` for local development (default, no infra); pick `cloudflare` when files are cached at Cloudflare's edge.

```sh
CDN_ADAPTER=noop
```
```sh
CDN_ADAPTER=cloudflare
CDN_API_TOKEN=cf-api-token-here
CDN_ZONE_ID=023e105f4ecef8ad9ca31a8372d0c353
CDN_BASE_URL=https://api.cloudflare.com/client/v4
```

Env vars: `CDN_ADAPTER`, `CDN_API_TOKEN`, `CDN_ZONE_ID`. The `cloudflare` adapter requires `api_token` and `zone_id` at construction; `base_url` defaults to `https://api.cloudflare.com/client/v4`. API tokens are never logged: display goes through `config.RedactedServices`. See the [cdn options](https://zenta-dev.github.io/zever/reference/config/#cdn) for every key.

## Purge content

Register the adapter, then resolve from the container, which resolves lazily, caches, and retries after an error; `container.New(nil)` is valid.

```go title="main.go" check
package main

import (
	"context"

	cdncloudflare "github.com/zenta-dev/zever/adapters/cdn/cloudflare"
	"github.com/zenta-dev/zever/config"
	"github.com/zenta-dev/zever/container"
	"github.com/zenta-dev/zever/core/cdn"
)

func main() {
	cdncloudflare.Register()
	ctx := context.Background()
	c := container.New(config.Default())
	defer c.Close(ctx)

	cd, err := c.CDN()
	if err != nil {
		panic(err)
	}

	if err := cd.Purge(ctx, cdn.PurgeRequest{
		URLs: []string{"https://example.com/assets/app.js"},
	}); err != nil {
		panic(err)
	}
	if err := cd.Purge(ctx, cdn.PurgeRequest{
		Tags: []string{"static", "v2"},
	}); err != nil {
		panic(err)
	}
	if err := cd.Purge(ctx, cdn.PurgeRequest{All: true}); err != nil {
		panic(err)
	}
}
```

A zero `PurgeRequest` is a no-op and returns an error. `All: true` purges the entire zone. The interface also has `Close(ctx)` and `Name()`.

## Options

| Option | Type | Default | Description |
|---|---|---|---|
| `api_token` | string | `""` | Cloudflare API token. Required by the `cloudflare` adapter. Never logged |
| `zone_id` | string | `""` | Cloudflare zone identifier. Required by the `cloudflare` adapter |
| `base_url` | string | `https://api.cloudflare.com/client/v4` | Cloudflare API endpoint override |

The `cloudflare` adapter has no retry logic: provider errors are returned to the caller. See the [cdn options](https://zenta-dev.github.io/zever/reference/config/#cdn) for every key.

## Choose an adapter

| Adapter | Package | Notes |
|---|---|---|
| `noop` | `adapters/cdn/noop` | Default, discards purges, closes instantly |
| `cloudflare` | `adapters/cdn/cloudflare` | Calls the Cloudflare cache-purge API via `cloudflare-go/v7`; no retry logic |

Both serve one interface, so switching is config-only. See the [adapters matrix](https://zenta-dev.github.io/zever/reference/adapters-matrix/).

## Troubleshooting

<details>
<summary>`Purge` returns `cdn: empty purge request`</summary>

Cause: a zero `PurgeRequest` with no URLs, tags, or `All`. Fix: set at least one field.
</details>

<details>
<summary>`cloudflare: option "api_token" is required`</summary>

Cause: the `cloudflare` adapter was selected without credentials. Fix: set `api_token` and `zone_id` in config or env (`CDN_API_TOKEN`, `CDN_ZONE_ID`).
</details>

<details>
<summary>Purge succeeds but visitors still see stale content</summary>

Cause: the URL or tag does not match what the edge cached. Fix: purge by exact URL or tag, or use `All: true` for the whole zone.
</details>

## Related

- [Cache](https://zenta-dev.github.io/zever/guides/data/cache/)
- [Storage and media](https://zenta-dev.github.io/zever/guides/data/storage-media/)
- [Media processing](https://zenta-dev.github.io/zever/guides/data/media/)
