ResilientLang docsDeveloper documentation (draft, product name is a placeholder)

Edge delivery

Status. Edge delivery is package WP-57 (integrations/edge, branch wp/WP-57-edge-delivery), not yet merged when this page was written. It was built and tested offline: in Node, in the real Cloudflare Workers runtime (workerd, through Miniflare) and in Chromium against the real loader. Nothing is deployed anywhere, no account was used, nothing is published. The Vercel, Netlify and Deno adapters are thin and untested on the real platforms (verify). The cost and limit figures below come from the package's README, which took them from Cloudflare's documentation on 2026-10-29; I did not fetch them again, so treat every figure as verify.

Edge delivery serves /<lang>/... (for example /fr/pricing) as real, translated HTML from your own hostname for sites that cannot be prerendered at build time: WordPress, server-rendered shops, any site behind a CDN that supports edge code. The edge fetches your original page, applies the signed bundle, sets lang and dir, a self-referencing canonical link and reciprocal hreflang, and returns it. Crawlers and link previews see translated text without running JavaScript.

When to use it#

You haveUse
A static or framework site you build yourselfBuild-time prerender: no runtime and no running cost
A site where you control the CDN or edge (Cloudflare, Vercel, Netlify, Deno) and want translated URLs for search enginesEdge delivery
No server-side control (Wix, Squarespace, Webflow, a script only)The script snippet; read SEO and the script snippet first

How a request is handled#

GET /fr/pricing
  1. first path segment is a language prefix of the verified manifest?  no -> your origin, untouched
  2. fetch GET /pricing from your origin (full page, Accept-Language pinned to the source language)
  3. verified bundle (signature, sha256, key epoch) -> translate with the extraction-spec engine
  4. <html lang dir data-rl-ssr>, canonical (self), hreflang for every language + x-default, links /about -> /fr/about
  5. weak ETag, capped Cache-Control, Content-Language, x-rl-edge header; cached per site key + bundle version + language + URL
ANY failure in 2-5 -> the origin page for /pricing as it is (source language), 200, marked and not cacheable

The language is decided by the URL only, never by Accept-Language or cookies, so there is no Vary: Accept-Language and no automatic redirect by language (Google advises against it).

What it will not break#

TrafficBehaviour
Forms and POST, PUT, DELETE under /fr/...Forwarded to the origin path with the client's own body, headers, cookies and Authorization. Same-site redirects get the prefix (/thanks becomes /fr/thanks); Set-Cookie passes through
API calls, wp-admin, wp-json, api, checkout, cart, account, framework folders (_next, _nuxt, _astro), robots.txt, sitemap*.xmlForwarded untouched (the default exclude list; RL_EXCLUDE replaces it)
Assets and files (anything with an extension other than html, htm, php, asp, aspx, jsp), also with RangeForwarded with the client's headers
Pages with cookiesRequest cookies reach the origin; such a page is never cached by the edge
Conditional requestsThe translated page has its own weak ETag, so If-None-Match gets 304. A Range request on a page URL gets the full 200
robots.txtA path disallowed for * gets no translated copy. rl-edge robots generates the mirrored rules for the language folders
noindex (X-Robots-Tag or a meta robots tag)Translated with the same robots signals kept, but no canonical and no hreflang are added. RL_TRANSLATE_NOINDEX=false serves such pages untouched. Keep them out of your sitemap
Query stringsTranslated, but no canonical or hreflang is added

Fail open: reading the x-rl-edge header#

Every response the edge touches carries x-rl-edge. Anything other than translated means "served from your origin".

x-rl-edgeMeaning
translated; lang=fr; v=12; cov=0.97Normal. v is the bundle version and cov the share of strings translated. ; cache=hit when served from the edge cache
bypass; reason=no-bundleNo verified manifest (every bundle origin down or invalid, or a cold start). Set RL_LANGS so /fr/... still falls back to the source page instead of reaching your origin as an unknown URL
reason=no-language-fileThe language file is missing, or its SHA-256 or version does not match the signed manifest
reason=robots, excluded, method, not-a-page, noindexDeliberately untouched (see above)
reason=coverageThe page is translated less than RL_MIN_COVERAGE
reason=too-large, charset, fragment, unsupported-html, not-html, errorA page it cannot or should not translate: over RL_MAX_HTML_BYTES (2 MiB), not UTF-8, not a full document, or an unexpected exception

Pages that could have been translated but were not also get Cache-Control: no-store and a Link: <source URL>; rel="canonical" header, so a source-language copy under a language URL is neither cached nor indexed as the translation. The only 5xx the edge can produce is 502 when your origin cannot be reached at all. A broken configuration turns the Worker into a plain pass-through. Rolling back means removing the route: source pages never depended on the Worker.

Trust#

The edge applies the same rules as the browser loader: manifest parse limits, the Ed25519 signature over the canonical manifest with your public key(s), key epochs, rollback refusal and the SHA-256 of every language file. There is no expiry, so a verified bundle keeps serving while the origins are down. A stale mirror serving an older version is skipped. Nothing secret is configured anywhere; never put an rl_sec_ key in the configuration.

Deploy on Cloudflare#

These are steps for you or the project owner; the repository never deploys. You need a Cloudflare account with your site's zone (Workers Paid recommended, see the limits below) and a published, signed bundle with your site key, public key and bundle origins.

  1. cd integrations/edge && npm ci && npm run build produces dist/worker.js (about 64 KB gzipped including the HTML parser).
  2. Copy templates/wrangler.toml.template to a deployment folder as wrangler.toml, copy dist/worker.js beside it, fill the [vars] (all public values) and the routes for your hostname.
  3. Check the file offline: node bin/rl-edge.mjs check-config wrangler.toml.
  4. Optional: create a KV namespace and add the binding (a shared verified copy of the bundle, fewer origin fetches).
  5. wrangler deploy is your decision on your account. Then test: curl -sI https://www.example.com/fr/ | grep -i x-rl-edge should say translated.
  6. Add Sitemap: https://www.example.com/rl-sitemap.xml to robots.txt, submit it in Search Console, and add the mirrored rules from rl-edge robots --in robots.txt --lang fr,de.

Other platforms (verify each on the real platform): Vercel Edge Middleware, a Netlify Edge Function and Deno Deploy templates exist and use the same handler. On middleware platforms the origin page is fetched from the site itself with a loop-guard header; Deno Deploy needs RL_ORIGIN_BASE.

Configuration (all public values)#

VariableDefaultMeaning
RL_SITE_KEYrequiredrl_site_live_... or rl_site_test_...
RL_PUBLIC_KEYSrequiredEd25519 public key(s), base64url, space or comma separated, oldest first, optional kid=key
RL_BASESrequired1 to 5 https bundle origins, tried in order
RL_SOURCE_LANGenLanguage of the original pages
RL_SITE_URLrequest originPublic origin for canonical, hreflang and the sitemap
RL_ORIGIN_BASErequest originWhere original pages come from (required on Deno Deploy)
RL_LANGSnoneLanguage codes known before the first bundle fetch (recommended)
RL_HYDRATIONautoauto strips framework hydration scripts on pages that hydrate; also ssr, loader, strip
RL_MIN_COVERAGE00 to 1; below it the page is served untouched
RL_REWRITE_LINKStrueRoot-relative links to page-like paths get the prefix; assets, files, forms and excluded areas do not
RL_DECORATE_SOURCEfalseAdd hreflang to source-language pages by streaming head injection. Off by default: the sitemap carries the reciprocal annotations and source pages stay byte-identical
RL_EXCLUDE, RL_PRIVATE_COOKIESsee the packagePaths left untouched; cookies that make a request visitor-specific
RL_CACHE_TTL, RL_MAX_STALE, RL_MANIFEST_TTL, RL_MAX_HTML_BYTES300, 300, 300, 2 MiBCache lifetime when the origin gives none; cap on cache lifetime; manifest refresh interval; page size cap
RL_SITEMAP_PATH/rl-sitemap.xmlWhere the translated sitemap hint is served

SEO helpers#

Limits and costs (verify all of this before quoting it)#