Skip to content
better-i18n.com

The CDN delivers your translations globally with low latency. This guide covers the URL shape, the cache behaviour, and how the SDK fetches from it.

How CDN delivery works #

Code
You publish translations
  → JSON files written to R2 origin storage
  → Edge cache purged for the affected paths
  → A user requests a translation file
  → The nearest edge serves it (X-Cache-Status: HIT)

Translations are served as JSON from https://cdn.better-i18n.com. CDN delivery is on by default — your SDK already points at it, there is nothing to enable.

CDN URL format #

Code
https://cdn.better-i18n.com/{org}/{project}/manifest.json
https://cdn.better-i18n.com/{org}/{project}/{locale}/{namespace}.json
https://cdn.better-i18n.com/{org}/{project}/{locale}/batch.json

Most projects have a single file per locale, and it is called translations.json:

Code
https://cdn.better-i18n.com/acme/dashboard/en/translations.json
https://cdn.better-i18n.com/acme/dashboard/tr/translations.json

If your project keeps its keys in namespaces, each namespace becomes its own file — en/common.json, en/dashboard.json — and batch.json returns several of them in one request, which is what the SDK uses to avoid a fetch per namespace.

The same paths work with the project UUID in place of {org}/{project}, which is what the SDK falls back to if a slug changes under it.

Verify a project by hand:

Bash
curl -i https://cdn.better-i18n.com/your-org/your-project/en/translations.json

Renaming a project doesn't break live apps #

When a project slug changes, a redirect marker is written at the old path. Requests to the old CDN URL answer 301 to the new one for 30 days, so a deployed app keeps working until you ship the new slug. After that window the old path is gone — treat the 30 days as a migration budget, not a permanent alias.

Cache headers #

What the edge actually returns:

PathCache-Control
{locale}/{namespace}.jsonpublic, max-age=3600
{locale}/batch.jsonpublic, max-age=60, s-maxage=60

Every response carries X-Cache-Status: HIT or MISS.

There is no stale-while-revalidate on these responses, and that is deliberate rather than an omission: Cloudflare's Cache API does not hand back a stale entry once max-age has passed, so advertising SWR would describe a behaviour you would not get. Freshness comes from the purge on publish, not from a revalidation window.

SDK fetch behaviour #

In @better-i18n/next the two knobs are Next.js ISR revalidation windows, not an in-memory TTL:

OptionDefault (production)Default (dev)
messagesRevalidateSeconds50
manifestRevalidateSeconds36000
TypeScript
export const i18n = createI18n({
  project: "acme/dashboard",
  messagesRevalidateSeconds: 30,
});

Zero in development is on purpose — you want an edit to show up on the next request while you are working, and you want caching in production.

Monitoring #

Per-project CDN metrics — request volume and cache hit rate — are on the project's overview page.

Platform-wide CDN status is at status.better-i18n.com, which you can subscribe to for outage notifications.

Serving from your own domain #

Not self-serve today. If a strict CSP means you cannot call cdn.better-i18n.com, talk to support rather than proxying it yourself — a proxy in front of the CDN usually ends up caching translations twice with two different lifetimes, which is worse than the problem it solves.