Description
Related to #2584 but a distinct root cause, not a rapid navigation race condition. This crash occurs on a single normal navigation when cacheComponents: true is enabled in Next.js 16, and only in production builds (Webpack). Never reproduces in dev mode (Turbopack).
When Next.js cacheComponents: true is enabled, routes are wrapped in React's <Activity> component. Activity runs useEffect cleanups on hide (navigation away) and re-runs effects on reappear (navigation back).
The Map component's cleanup calls mapbox.destroy() → map.remove(), which sets _container = null and _removed = true on the Mapbox instance. When Activity reappears the route, Marker's useEffect re-fires and calls marker.addTo(map.getMap()). However, map.getMap() returns the destroyed instance with _container = null, resulting in a crash.
Unlike #2584 where _container exists but child elements are partially torn down, here _container is fully null because map.remove() ran to completion during the Activity hide phase.
Expected Behavior
Map and Marker components should reinitialize cleanly when Activity reappears the route. The Mapbox instance should be recreated after it was destroyed during the hide phase.
Steps to Reproduce
- Create a Next.js 16 app with
cacheComponents: true in next.config.ts.
- Create a page with
<Map> and <Marker> components from react-map-gl.
- Build for production (
next build && next start) - this does not reproduce in dev mode.
- Navigate to the map page → renders fine ✅.
- Navigate away to any other route.
- Navigate back to the map page.
Crash on second visit:
TypeError: Cannot read properties of undefined (reading 'appendChild')
at Marker.addTo
at recursivelyTraverseReappearLayoutEffects
The stack trace recursivelyTraverseReappearLayoutEffects confirms React Activity's reappear phase is the trigger, not a standard remount.
Environment
- Framework version:
react-map-gl@8.1.0 (via @vis.gl/react-mapbox)
- Map library:
mapbox-gl@3.x
- Next.js: 16 (App Router,
cacheComponents: true, production/Webpack only)
- React: 19
- Browser: Chrome
- OS: Windows 11
Logs
TypeError: Cannot read properties of undefined (reading 'appendChild')
at Marker.addTo (mapbox-gl)
at eval (marker.ts — useEffect calling marker.addTo(map.getMap()))
at recursivelyTraverseReappearLayoutEffects (react-dom)
at reappearLayoutEffects (react-dom)
Confirmed via instrumentation:
map.getMap() returns the Mapbox instance ✅ (truthy)
map.getMap()._removed === true ❌ (destroyed during Activity hide)
map.getMap()._container === null ❌ (set to null by map.remove())
Description
When Next.js
cacheComponents: trueis enabled, routes are wrapped in React's<Activity>component. Activity runsuseEffectcleanups on hide (navigation away) and re-runs effects on reappear (navigation back).The Map component's cleanup calls
mapbox.destroy()→map.remove(), which sets_container = nulland_removed = trueon the Mapbox instance. When Activity reappears the route, Marker'suseEffectre-fires and callsmarker.addTo(map.getMap()). However,map.getMap()returns the destroyed instance with_container = null, resulting in a crash.Unlike #2584 where
_containerexists but child elements are partially torn down, here_containeris fullynullbecausemap.remove()ran to completion during the Activity hide phase.Expected Behavior
Map and Marker components should reinitialize cleanly when Activity reappears the route. The Mapbox instance should be recreated after it was destroyed during the hide phase.
Steps to Reproduce
cacheComponents: trueinnext.config.ts.<Map>and<Marker>components fromreact-map-gl.next build && next start) - this does not reproduce in dev mode.Crash on second visit:
The stack trace
recursivelyTraverseReappearLayoutEffectsconfirms React Activity's reappear phase is the trigger, not a standard remount.Environment
react-map-gl@8.1.0(via@vis.gl/react-mapbox)mapbox-gl@3.xcacheComponents: true, production/Webpack only)Logs
Confirmed via instrumentation:
map.getMap()returns the Mapbox instance ✅ (truthy)map.getMap()._removed === true❌ (destroyed during Activity hide)map.getMap()._container === null❌ (set to null bymap.remove())