Fix blank geo map under maplibre-gl 6: serve its worker from public/ - #8
Merged
Merged
Conversation
…map) Regression from the 5.24.0 -> 6.4.1 bump (#5). maplibre-gl 6 added an internal ResizeObserver on the `container` option (_setupResizeObserver, new in this major version) that calls resize() + redraw() on its own. We pass the SAME element as container that useMapViewportSync CSS-transforms every frame for the zoom sync (translate + scale, to keep the background locked to React Flow's viewport without a jumpTo() per frame) -- and that hook already has its own ResizeObserver on that element, calling resize() explicitly at every point that matters: on load, on tile style change, and on a real container resize. Two independent resize-tracking paths racing on the one CSS-transformed element is what made zooming visibly stretch/narrow the map canvas instead of just scaling it smoothly. trackResize: false disables only maplibre's own new auto-resize path (confirmed its only consumer in the library). Nothing else depends on it -- our code already explicitly calls .resize() at every point maplibre's internal tracking would have. Verified: tsc 0 errors, 230/230 tests, eslint 0 new warnings, clean build. Visual confirmation (does zoom now scale smoothly) still needs an actual browser -- please check after pulling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
maplibre-gl 6 ships ESM-only and loads tile decoding into a Web Worker that imports a sibling chunk (maplibre-gl-shared.mjs) by relative path. Next.js — in both Turbopack and --webpack mode — emits that worker as a lone hashed asset without the sibling, so the worker throws on its first import. The map mounts and fires "load", but never requests a tile: a blank background, no console error, and the CSS-transform viewport sync left as the only thing moving (hence "zoom pans the window, not the map"). Follow the upstream Turbopack/Next.js recipe: copy both dist files into public/maplibre/ at predev/prebuild time (always matching the installed version) and point setWorkerUrl at the same-origin path. Caddy already allows worker-src 'self' and leaves /maplibre/* on no-cache, so an upgrade can't serve a stale worker. Also reverts the speculative trackResize:false from the previous commit — it addressed a hypothesis, not this cause, and maplibre's own resize tracking is harmless. Docs: architecture.md gains the build step and the version bump to 6. Ref: https://maplibre.org/maplibre-gl-js/docs/ → Installation → Turbopack Signed-off-by: Cristian Curaba <cristiancuraba00@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
maplibre-gl 6 ships ESM-only and loads tile decoding into a Web Worker. That worker imports a sibling chunk,
maplibre-gl-shared.mjs, by relative path.Next.js — in both Turbopack and
--webpackmode — turns the usualnew URL(..., import.meta.url)worker reference into a single hashed asset without emitting the sibling next to it. The worker throws on its first import, and, quoting the upstream docs, "the map mounts but never requests a tile".That single fault explains every symptom reported:
useMapViewportSyncis the only thing still movingΔlngnever convergesNo console error, which is why it stayed hidden.
Fix
The upstream-documented Turbopack/Next.js setup (docs → Installation → Turbopack):
scripts/copy-maplibre-worker.mjscopies both dist files intopublic/maplibre/onpredev/prebuild— fromnode_modules, so it always matches the installed version.setWorkerUrl("/maplibre/maplibre-gl-worker.mjs")at module scope ingeo-map-background.tsx.public/maplibre/is gitignored (generated) and ESLint-ignored (vendor code).Reverts the speculative
trackResize: falsefrom b83d098 — it addressed a hypothesis, not this cause.Rest of the v5→v6 migration guide
Audited every other breaking change; none apply: no
map.transform, nostyleimagemissing, noJSON.parseon feature properties, no#pragma mapbox, noGeoJSONSource.setData, norequire('maplibre-gl'), noinstanceofon events. WebGL2-only is a browser-support change, not a code change.Deploy path
out/(verified) → Docker/Caddy serve them.worker-src 'self'; the URL is same-origin.@immutablecaching is scoped to/_next/static/*, so/maplibre/*staysno-cache— an upgrade can't serve a stale worker.Verification
tsc --noEmit,npm run lint(0 errors),npm test(230 passed),npm run buildall green. Dev server serves both files at200 application/javascript.Needs a browser check on a georeferenced Canvas to confirm tiles now render.