# Content Deploy / CDN

How Comvi deploys translations to the edge: URL format, the Content Deploy settings, automatic and manual deploys, cache behaviour, and recovery.

Canonical: https://comvi.io/docs/platform/cdn

Content Deploy turns a project's translations into JSON files at predictable URLs on
Cloudflare's edge. Enable it under **Settings → Content Deploy**; the endpoint URL appears
on the same page with a copy button once it is enabled and saved.

## URL format

```text
https://cdn.comvi.io/<project-token>/<language>.json                # default namespace
https://cdn.comvi.io/<project-token>/<namespace>/<language>.json    # any other namespace
```

```text
https://cdn.comvi.io/abc123/en.json               # default namespace, English
https://cdn.comvi.io/abc123/checkout/fr.json      # checkout namespace, French
https://cdn.comvi.io/abc123/emails/pt-BR.json     # emails namespace, Brazilian Portuguese
```

The token is project-scoped and generated for you. Each file is named after the canonical
BCP-47 code the project stores, and capitalization matters: `pt-BR`, not `pt-br`. An
underscored or lowercase spelling such as `pt_BR.json` is a temporary alias; request the
canonical name.

Each file is a flat JSON map from key to value, served compact with
`Content-Type: application/json`, and no authentication: anyone with the URL can read the
bundle. Treat the token as a secret if the translations themselves are sensitive.

`https://cdn.comvi.io/abc123/en.json`

```json
{
"greeting": "Hello, {name}!",
"nav.home": "Home",
"nav.settings": "Settings"
}
```

## CDN settings

**Settings → Content Deploy** controls what is deployed:

| Setting | Effect |
|---|---|
| **Content Deploy** | Master switch. Off means nothing is deployed and no endpoint URL exists |
| **Locales** | Subset of project locales to include. An unticked locale is not deployed at all |
| **Namespaces** | Subset of namespaces to include |
| **Filter by Status** | Which statuses to include: **Translated**, **Needs Review**, **Not Translated**. Empty by default, and empty deploys every status |

A project has exactly one live configuration; one project cannot serve two endpoints with
different filters. The dashboard deploys plain JSON; the REST API's `cdnSettings.format` also accepts `i18next-v4` ([Projects API](/docs/api/projects)).

> **The default filter ships drafts**
>
> With Content Deploy on and every status box unchecked, a value you save reaches the CDN
> about 5 seconds later with no further action. Tick **Translated** on its own to keep
> unreviewed values off the edge. See
> [Translation lifecycle](/docs/platform/translation-lifecycle).

## Automatic and manual deploys

| Mode | Trigger | Delay |
|---|---|---|
| **Automatic** | Any translation edit, import or status change | 5 s after the last edit, capped at 1 min from the first edit in a burst |
| **Manual** | **Deploy Now** next to the CDN endpoint | Immediate |

Every new edit pushes the scheduled deploy forward by 5 seconds, up to a ceiling of 1
minute, so a burst of edits becomes one job and a long editing session cannot starve the
deploy. **Deploy Now** while an automatic deploy is pending cancels the pending job and runs
immediately. Only one deploy runs per project at a time; a manual deploy is ignored while
another is in flight.

<Mermaid
code={`sequenceDiagram
    participant U as User
    participant P as Platform API
    participant S as R2 Storage
    participant C as CDN Edge
    participant D as Dashboard

    U->>P: Click Deploy Now
    Note over P: Build per-namespace per-language bundles
    P->>S: Upload bundles
    P->>C: Purge cache keys
    P->>D: Send deploy status update
    P-->>U: Deploy complete
`}
caption="A deploy builds one file per selected (language, namespace), uploads to R2, deletes files that left the snapshot, purges the edge cache for every affected URL, and updates the deploy record."
/>

A deploy usually finishes in a few seconds. Its result is recorded on the Content Deploy
page and through the REST API: `success`, `partial` (some files failed under the threshold,
or the build produced warnings such as an ICU message that could not be converted), or
`failed`, with the files uploaded, any warnings and the error message. The last deploy
timestamp and result are stored on the project.

## Caching

```text
Cache-Control: public, max-age=30, must-revalidate, stale-if-error=604800
```

A deploy purges every file it touches at the edge, so the next request gets the fresh bundle.
A browser copy is at most 30 seconds stale and can be served for up to a week if the origin
is unreachable. The edge holds a copy for up to 24 hours, which is why the purge, not the
max-age, delivers a change within seconds. A browser cache and any CDN of your own are
untouched by the purge.

If a read looks stale: confirm Content Deploy is enabled, the locale and namespace are
ticked, the value's status matches the filter, and bypass your own client cache.

## Failed deploys and recovery

A deploy with more than 5% of its files failing is abandoned after the upload pass. The files
that uploaded are already live, the rest are not, so the edge holds a mix of old and new
bundles until the next successful deploy. Transient R2 errors are the usual cause, and a
second deploy clears them; format-conversion errors need the source values fixed first.

There is no rollback button. To undo a bad deploy:

1. Revert the offending values with the editor's history.

2. Click **Deploy Now**, or wait for the automatic deploy the edits already scheduled.

Every change stays in history, so the rollback is auditable. For versioned snapshots as a
compliance requirement, or for a CDN that must not be readable by anyone holding the URL,
contact [hello@comvi.io](mailto:hello@comvi.io).

## Next steps

- [Connect your app](/docs/i18n/connect-comvi): Point the Comvi i18n library at this endpoint.
- [Fetch loader](/docs/i18n/plugins/fetch-loader): The plugin that reads the endpoint: caching, retries and failure modes.
- [Consume translations](/docs/platform/consume-translations): Load bundles into any app: web, mobile, backend.
- [Webhooks](/docs/platform/integrations/webhooks): React to translation, key, namespace, import and export events.
