A contact page embeds an interactive map, the map SDK and its tiles account for most of the page’s weight, and the overwhelming majority of visitors look at the pin and never touch it.

Triage: Measure the Unused Case

The measurement that matters is the page as the majority experiences it — loaded, looked at, never interacted with.

  1. Load the page in an incognito window with the Network panel open, throttled to Slow 4G, and do not touch the map.
  2. Filter to the map vendor’s origins and record the request count and total transfer.
  3. Record Total Blocking Time from a Performance trace of the same load.
  4. From your analytics, find the share of sessions on this page that produce any map interaction at all.
What an untouched map costs A typical interactive map embed on an untouched page load: the SDK and its dependencies account for roughly four hundred kilobytes, tile images for a further two hundred, and about two hundred and forty milliseconds of blocking time. A static tile with a link and a button costs about forty kilobytes and negligible blocking time, and looks identical until someone tries to pan. interactive embed SDK + deps ≈ 400 KB tiles ≈ 200 KB blocking ≈ 240 ms paid by every visitor static tile facade one image ≈ 40 KB no SDK blocking ≈ 0 ms identical until someone pans The two are visually indistinguishable at rest. Only one of them can be dragged.

Root Cause: The Interactive Layer Is Loaded for a Static Purpose

A map embed exists to answer a question — where is this place — and for most visitors a picture answers it. The interactive layer supports panning, zooming, route planning and search, none of which the majority use, and all of which require the vendor’s full runtime plus a tile-fetching pipeline plus, frequently, a WebGL context.

That makes it an unusually clean case for the facade pattern: the pre-interaction state is reproducible as a single image, the geometry is fixed and known, and the interaction that signals genuine intent — a click or a drag on the map itself — is unambiguous. Video embeds are similar; maps are more favourable, because a static map image is an official product of every major provider rather than something you have to construct.

The consent dimension usually applies too. Map providers set identifiers and record requests, which places them in a gated category on most sites — so an eagerly loaded map is frequently both a performance problem and a compliance one, and the facade addresses both by replacing the request with an image served from your own origin.

Three ways to render a location Three levels of map implementation. A static image from the provider static-map API, served through your own origin, shows the location with no vendor request from the visitor. A static image plus a link to the provider offers directions without loading anything. And the full interactive embed supports panning and search, and costs the SDK, the tiles and the blocking time. Most pages need the first or second. static image, self-hosted shows the place — no vendor request from the visitor at all static image + directions link answers "how do I get there" without loading anything full interactive embed panning and search, at the SDK plus tiles plus blocking time The middle row satisfies most contact pages entirely, and costs one image.

Resolution: A Static Tile That Becomes a Map

<!-- The wrapper reserves the exact geometry, so the swap cannot shift layout. -->
<div class="map" style="aspect-ratio: 16 / 10; position: relative;">
  <button class="map__facade" type="button"
          aria-label="Load interactive map of 12 Example Street, London"
          style="position:absolute; inset:0; width:100%; height:100%; padding:0; border:0; cursor:pointer;">
    <!-- Generated at build time from the provider's static-map API and served
         from your own origin, so no vendor request occurs before interaction. -->
    <img src="/img/maps/office.webp" alt=""
         width="1280" height="800" loading="lazy" decoding="async"
         style="width:100%; height:100%; object-fit:cover;">
  </button>

  <template class="map__target">
    <iframe src="https://maps.vendor.example/embed?q=12+Example+Street"
            title="Interactive map of 12 Example Street, London"
            style="position:absolute; inset:0; width:100%; height:100%; border:0;"
            loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
  </template>
</div>

<!-- Always present: the answer most visitors actually want, with no JavaScript. -->
<p><a href="https://maps.vendor.example/dir/?destination=12+Example+Street">Get directions</a></p>
document.querySelectorAll('.map').forEach((root) => {
  const button = root.querySelector('.map__facade');
  const target = root.querySelector('.map__target');
  let swapped = false;

  button?.addEventListener('click', () => {
    if (swapped) return;                   // a double-tap must not insert two maps
    swapped = true;
    root.appendChild(target.content.cloneNode(true));
    button.remove();
    root.querySelector('iframe')?.focus({ preventScroll: true });
  }, { once: true });

  // Free head start while the pointer travels; no payload, no consent question.
  const warm = () => {
    if (document.querySelector('link[data-map-preconnect]')) return;
    const l = document.createElement('link');
    l.rel = 'preconnect'; l.href = 'https://maps.vendor.example';
    l.crossOrigin = 'anonymous'; l.dataset.mapPreconnect = '';
    document.head.appendChild(l);
  };
  button?.addEventListener('pointerenter', warm, { once: true, passive: true });
  button?.addEventListener('touchstart', warm, { once: true, passive: true });
});

The directions link outside the facade is the detail that makes this a better page rather than merely a lighter one. It answers the question most visitors came with, it works without JavaScript, and it is reachable by keyboard in one tab — none of which is true of an interactive map that requires panning to reveal an address.

Verification: Nothing From the Map Origin

Load with the Network panel filtered to the map provider’s origins and do not touch the map. The filter must stay empty. Click once: the map loads and is interactive. Record a Performance trace during the swap and confirm no layout-shift entry.

Three checks on the swap Three verifications. With the map untouched, the network filter for the provider origins must be empty. After one click, the map must load and be interactive. And a performance trace across the swap must show no layout-shift entry, confirming the wrapper reserved the correct geometry. untouched no provider requests the filter stays empty one click map loads, interactive one insertion only across the swap no layout-shift entry geometry reserved Generate the static image at build time — fetching it at runtime reinstates the request you removed.

Generating the Static Image

Every major map provider offers a static-map endpoint that returns a rendered image for a location, a zoom level and a size. Call it once at build time, commit the result, and serve it from your own origin — that is what keeps the pre-interaction state entirely first-party.

Fetching it at runtime instead defeats the purpose completely. A static image requested from the provider’s domain is still a request to the provider, still carries the visitor’s address and user agent, and still raises the same consent question as the embed. The saving would be bandwidth alone, which is the smaller half of the problem.

Size the image for the box it actually renders in rather than at maximum resolution, and prefer a modern format. A contact page rendering a map at 640 pixels wide does not need a 1280-pixel image except for high-density displays, where a two-times variant is sufficient — and the difference between a well-sized WebP and an unoptimised PNG is frequently larger than everything else you saved.

Choosing the Zoom and the Crop

A facade only works if the static image answers the same question the map did, and that depends almost entirely on the zoom level. Too far out and the pin floats in an unrecognisable region; too far in and the surrounding streets that let someone orient themselves are cropped away.

The level that works for a business address is usually the one where the street name and one or two neighbouring landmarks are legible — close enough to be specific, wide enough to be placeable. Generate two or three candidates during the build and look at them rather than picking a number from the documentation, because the right value varies with how dense the area is.

The crop matters on mobile in particular. A 16:10 image cropped to a narrow viewport can push the pin off-centre or out of frame entirely, which turns the facade into a picture of somewhere else. Generate a second image at the mobile aspect ratio and swap by media query — two build-time images, and the facade renders correctly at both ends of the range.

Common Pitfalls

  • Fetching the static image from the provider at runtime. It reinstates the vendor request, the consent question and the third-party origin.
  • Omitting the directions link. The facade then answers less than the embed did, which is a regression rather than an optimisation.
  • Reserving the box on the image rather than the wrapper. A provider stylesheet or the iframe’s own dimensions can then change the geometry at the swap.
  • Using a div with a click handler. The facade must be a button to be reachable by keyboard and announced correctly.

Frequently Asked Questions

Does the provider's licence allow caching a static map image?

It varies by provider and it is worth checking rather than assuming, because the terms for static-map endpoints frequently differ from those for the interactive embed. Some permit caching for a defined period, some require the image to be requested per view, and some price the endpoint per request in a way that makes build-time generation attractive to them as well.

Where caching is not permitted, the pattern still works with a proxy on your own origin that fetches and forwards the image — the visitor’s browser contacts only you, which preserves the privacy and connection properties, while each render is still a request to the provider from your server. That is a middle option worth knowing about before abandoning the approach.

What about a map that is the primary content?

Then load it eagerly. A store locator, a delivery-area checker or a route planner is the reason the visitor is on the page, and deferring it trades a real regression in the experience for a synthetic improvement in a lab metric. The facade is for maps that illustrate rather than for maps that are the product.

The test is the same as for any embed: would most visitors interact with it within the first few seconds? If yes, the facade adds a round trip at the moment of interaction and helps nobody. If no — which describes almost every contact page — the facade is close to free.

How should the facade behave when consent is refused?

Keep the static image and the directions link, and replace the load action with a short explanation. The image is first-party and raises no consent question, so a refusal costs the visitor only the interactive layer — which is a far better outcome than an empty box, and better than the interactive map they declined.

Avoid a button that silently does nothing. A control that appears actionable and is not reads as a broken page and generates support requests; a line of text saying the interactive map needs the vendor’s cookies, with a way to change that decision, respects both the refusal and the visitor.


Up: Lazy-Loading Third-Party Embeds on Interaction