Lazy-load Google Maps to reduce billable Maps JavaScript API usage - #1470
Closed
lgarofalo wants to merge 1 commit into
Closed
Lazy-load Google Maps to reduce billable Maps JavaScript API usage#1470lgarofalo wants to merge 1 commit into
lgarofalo wants to merge 1 commit into
Conversation
Both SearchMap and MapOfLocations mount a <GoogleMap> unconditionally on every page render, which fires a billable Maps JavaScript API "map load" event even for visitors who never scroll to or look at the map. This is especially costly on service/organization detail pages, where the map is rendered well below the fold (after About, Details, Contact Info, etc). Add a small useInView hook (native IntersectionObserver, no new dependency) and use it to defer mounting <GoogleMap> until its container is about to scroll into view (300px rootMargin so it loads just ahead of the user reaching it). A lightweight Loader placeholder of the same size is shown until then, so there's no layout shift. - MapOfLocations (service/org detail pages): this is the primary win, since this section is reliably below the fold. - SearchMap (search results page): applied for consistency/defense in depth, though this map is usually within the initial viewport by design (a persistent map alongside/above the results list), so savings here will be smaller. A bigger reduction here would require a product decision (e.g. click-to-load, or a list-first default on mobile) rather than a pure viewport check.
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.
Problem
Both
SearchMapandMapOfLocationsmount a<GoogleMap>(viagoogle-map-react, which loads the Maps JavaScript API) unconditionally on every render. Each mount is a billable Google Maps Platform "map load" event, and this happens for every visitor on every page view, even if they never scroll to or look at the map.This is especially wasteful on service/organization detail pages: the map (
MapOfLocations) is rendered in the "Location and Hours" section, well below the fold, after About, Service Details, and Contact Info. Any visitor who doesn't scroll that far still triggers a billed map load.Change
Added a small
useInViewhook (app/hooks/useInView.ts) using the nativeIntersectionObserverAPI (no new dependency). It reports once when a ref'd element scrolls near the viewport, then stops observing — intended for one-time lazy loads like this rather than continuous visibility tracking.MapOfLocations(service/org detail pages): now defers mounting<GoogleMap>until its container is within 300px of the viewport, showing the existingLoaderin the same fixed-height.mapcontainer until then (no layout shift). This is the primary win — this section is reliably below the fold on both mobile and desktop.SearchMap(search results page): applied the same pattern for consistency/defense-in-depth. Note this map is usually within the initial viewport by design (it's a persistent panel alongside the results list on desktop, and above the list on mobile), so the savings here will likely be smaller in practice. A bigger reduction on this page (e.g. click-to-load, or defaulting to list-first view on mobile) would be a product decision beyond a pure viewport check, and is out of scope for this PR.Testing
npx tsc --noEmit— passesnpx eslinton changed files — passesnpx prettier --checkon changed files — passesnpm test) has no spec files covering these components; the harness itself currently fails to boot in newer Node versions unrelated to this change (repo pins Node 18 via.nvmrc).Follow-ups not included in this PR
While researching this, I also found:
config.example.yml, and a second, different leaked key in a commented-out<script>tag inapp/index.html. Worth rotating/removing and adding HTTP-referrer restrictions in GCP console regardless of this change.StreetViewImageand the Static Maps image inServicePdfPagealso fetch directly from Google with no caching layer, and could be moved behind a backend cache/proxy since they're deterministic per-address.Happy to open separate PRs/issues for those if useful.