Skip to content

post: Your service worker's precache can't do Range requests — PMTiles needs 206 - #51

Open
clarkbw wants to merge 1 commit into
mainfrom
post/pmtiles-range
Open

post: Your service worker's precache can't do Range requests — PMTiles needs 206#51
clarkbw wants to merge 1 commit into
mainfrom
post/pmtiles-range

Conversation

@clarkbw

@clarkbw clarkbw commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Post: 2026-08-11-pmtiles-workbox-precache-range-requests-206-partial-content-service-worker-offline-maplibre-vite-plugin-pwa-runtimecaching-rangerequests-cachefirst.md

Draft — not published. The date: is the draft date (2026-08-11); the publisher bumps date + filename at merge (script/bump.py / script/publish.py).

What it covers

The slackwater-web PWA fix in 2e8c05f. Workbox's precache stores and replays a full 200 OK; pmtiles reads the archive with HTTP Range requests and requires 206 Partial Content. So a precached land.pmtiles rendered on the cold first visit — when no service worker was in the path — and threw on every visit after, with the map screen blank offline.

Arc: precache it (and hit the silent 2 MB maximumFileSizeToCacheInBytes cap) → a smoke test whose own comment named the bug and then ordered itself to avoid it → runtimeCaching + rangeRequests: true, which leaves the cache permanently empty → the fix.

The load-bearing finding: RangeRequestsPlugin implements only cachedResponseWillBeUsed, so it never touches the outbound request. A ranged first read misses, hits the network, gets a 206, and cacheableResponse: { statuses: [200] } refuses to store it — and you can't widen that, because the Cache API rejects a 206 on put(). Hence the non-ranged warm fetch in main.tsx. Sub-gotcha: registerType: 'prompt' doesn't set clientsClaim, so serviceWorker.ready resolves while controller is still null — the warm fetch has to also listen for controllerchange.

SEO targets

Slug/description/tags carry: pmtiles, Server returned no content-length header or content-length exceeding request, Check that your storage backend supports HTTP Byte Serving, 206 Partial Content, workbox precache range requests, rangeRequests: true, runtimeCaching, CacheFirst, cacheableResponse, maximumFileSizeToCacheInBytes, vite-plugin-pwa, registerType prompt, clientsClaim, controllerchange, maplibre, Partial response (status code 206) is unsupported.

Figures (3, hand-written SVG)

  • first-visit-vs-return-visit.svg — the mechanism: same byte-range read, 206 from the static host vs 200 from the precache, and where pmtiles throws.
  • cachefirst-range-deadlock.svg — why a ranged request can never fill a CacheFirst route, and how the warm fetch breaks the loop.
  • service-worker-control-timeline.svgready resolves before controller is set under registerType: 'prompt'.

Verification

  • python3 script/lint-liquid.pypost lint: 35 posts clean
  • bundle exec jekyll build → clean
  • All three <img src="/assets/img/…"> present in _site/<slug>/index.html; SVGs land in _site/assets/img/<slug>/; both post_url related links resolve.
  • Figures rendered to PNG and eyeballed for overflow/contrast.

No {% raw %} guard needed — the body has no {{ / {% outside the related-links footer.

Notes for review

  • Source quotes are verbatim from upstream: the pmtiles FetchSource.getBytes check (protomaps/PMTiles js/src/index.ts) and workbox-range-requests/src/RangeRequestsPlugin.ts (v7). Worth a glance since the post leans on both.
  • The console error is shown as the message pmtiles throws, without a fabricated stack frame.
  • No private infrastructure referenced — this is all public slackwater-web code.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HaMaRk9UijJQMDX48EVvTk

…s needs 206

Draft for review. Workbox precache serves a plain 200 with the full body and no
Range support; pmtiles reads the archive with Range requests and requires 206,
so a precached .pmtiles worked on the cold first visit and threw on every
return visit. Fix is a CacheFirst runtimeCaching route with rangeRequests:true
plus a non-ranged warm fetch, because RangeRequestsPlugin only implements
cachedResponseWillBeUsed and a ranged request can never populate the cache.
Three hand-written SVG figures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaMaRk9UijJQMDX48EVvTk
@clarkbw clarkbw added the publish Approved to publish via the drip-publisher routine label Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

publish Approved to publish via the drip-publisher routine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant