From ded4b3c10529cba3cfaca305ca918588f76000a1 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 26 Sep 2026 12:18:22 +0000 Subject: [PATCH] =?UTF-8?q?how-to:=20hosting=20the=20dx-maps=20folder=20?= =?UTF-8?q?=E2=80=94=20caching,=20compression=20and=20CORS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From exp-shepherd's web-boundaries report, checked against main (afb34488): no file in dx-maps/ is content-hashed today, so everything takes public, max-age=86400, stale-while-revalidate=604800 with an ETag, and immutable only once names are hashed. The boundary file is 1,866 KB raw / 597 KB gzipped (measured); the smaller -web copies are not on main yet, so not cited. Script embeds need ACAO * because every embed.script carries integrity + crossorigin=anonymous. The first-paint figures are rega's cold loads of the UPG-100 list (9.5-16.5 s) and studio's bundle/engine split. --- documentation/how-to.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/documentation/how-to.md b/documentation/how-to.md index 2283c817c..9d6f33bbb 100644 --- a/documentation/how-to.md +++ b/documentation/how-to.md @@ -234,6 +234,46 @@ snippets (leave it empty for relative URLs that work wherever the folder is drop --- +## Hosting the dx-maps folder: caching, compression, CORS + +**Why it matters.** On a cold load a map's layers draw about 10–17 s after the page opens (measured +on the UPG-100 list page). The bundle and the map engine take under 2 s of that; the rest is basemap +tiles and data files such as the country-boundary file (1,866 KB raw, 597 KB gzipped). Compression +cuts every visit; caching means a returning reader doesn't download the same files again. + +**Every file in `dx-maps/` has a fixed name today.** Nothing is content-hashed, so a new release +overwrites files in place: bundles (`/.js`), their pages (`index.html`) and +`manifest.json` change whenever the build changes them. Images, fonts and data files change rarely, +but under the same names. + +| Files | `Cache-Control` | Why | +|---|---|---| +| everything in `dx-maps/`, as it ships today | `public, max-age=86400, stale-while-revalidate=604800`, with an `ETag` | a revisit revalidates cheaply and never re-downloads unchanged bytes, and a release shows up within a day | +| content-hashed names, if you add them (e.g. `world-adm0.3f9a1c2e.geojson`) | `public, max-age=31536000, immutable` | a changed file gets a new name, so a year-long cache is always safe | + +**Never send `immutable` for a fixed name**: an update would stay invisible for a year. + +**Compress:** serve `br` or `gzip`, and give data files a JSON content type (`application/json`, or +`application/geo+json` for `.geojson`) so CDNs compress them. Many servers send `.geojson` as +`application/octet-stream`, which CDNs don't compress. + +**CORS for script embeds:** every `embed.script` snippet loads the bundle with an `integrity` hash +and `crossorigin="anonymous"`. For that to work on another site, the host must send +`Access-Control-Allow-Origin: *`; without it the browser can't check the hash across origins and refuses +the script. iframe embeds need no CORS. + +On Cloudflare Pages or Netlify, a `_headers` file at the folder's root sets the CORS and cache headers: + +``` +/* + Access-Control-Allow-Origin: * + Cache-Control: public, max-age=86400, stale-while-revalidate=604800 +``` + +Check the content type your host gives `.geojson` files (`curl -sI `) before relying on compression. + +--- + ## Compose maps into a SPA (the app shell) Several maps + one layout = a single-page app, served from the same `public/` tree.