The Problem
Pantone products are sold through a global network of authorized dealers. Customers need a way to find retailers near them, filtered by region and product type. I originally built the dealer locator in 2017 as a vanilla JavaScript application. Over the years it survived three map provider migrations and a complete frontend architecture change — and I was the developer through all of it.
The Original (2017)
The first version was a self-contained vanilla JS application I built from the ground up with Google Maps:
- Interactive map with custom pins and popups
- AWS Lambda API serving dealer data from the backend
- Country and state filtering with dynamic dropdown population
- Product group filtering via radio buttons (Graphics, Fashion, etc.)
- Map-list synchronization — clicking a pin highlights the dealer card, clicking a card pans the map
- Multi-language support across all Pantone regions
- Query string parameters for deep-linking to specific countries
It was a PantoneDealerLocator global class, initialized imperatively, running as a standalone script injected into the BigCommerce storefront.
Three Map Providers
The locator has lived through three map provider migrations — each one a significant rewrite of the mapping layer while keeping the rest of the application stable:
Google Maps → Bing Maps. The first migration was driven by licensing costs. Google Maps pricing had become expensive at Pantone's traffic volume. Bing Maps offered a more favorable enterprise agreement. The switch required rewriting all map initialization, pin rendering, popup behavior, and event handling — every map provider has its own API surface.
Bing Maps → Azure Maps. When Microsoft consolidated its mapping products under Azure, the Bing Maps API was deprecated in favor of Azure Maps. Another full rewrite of the mapping layer — new SDK, new authentication model (subscription keys instead of API keys), new tile rendering, new pin/popup APIs. The Azure Maps SDK also required different loading patterns (dynamic script injection vs. static script tag).
Through both migrations, the dealer data layer, filtering logic, and UI remained stable. The architecture I'd built in 2017 — with the map abstracted behind its own initialization and rendering functions — made these transitions possible without rebuilding the entire application.
The Rewrite (Next.js)
Seven years after the original build, the BigCommerce storefront moved to a headless Next.js architecture. The vanilla JS locator — still running via script injection with DOM manipulation and global namespaces — needed to become a modern React component.
Component decomposition. The single monolithic class became six focused components: DealerLocator (container), DealerFilters (country/state/product controls), DealerList (scrollable card list), DealerCard (individual dealer), DealerMap (Azure Maps wrapper), and PantoneWebsites (regional links).
Custom hooks for separation of concerns. useDealerData handles API fetching with caching. useDealerFilters manages filter state with memoized dealer lists — filters recalculate via useMemo instead of re-querying. useAzureMap handles the Azure Maps SDK lifecycle — lazy loading, map creation, pin management, and cleanup on unmount.
API route with ISR caching. A Next.js API route proxies requests to the existing AWS Lambda backend, adding 1-hour ISR revalidation and proper cache headers. The Lambda reads from Aurora with GSI indexes for country and active account queries.
TypeScript throughout. Every dealer record, filter option, map pin, and API response has a typed interface. The old code relied on runtime checks — the new code catches issues at build time.
Technical Complexity
Azure Maps SDK in React. The Maps SDK is designed for imperative use — create a map, add pins, handle events. Wrapping this in React's declarative model requires careful lifecycle management. The useAzureMap hook lazy-loads the SDK script, creates the map instance in a ref, synchronizes pins with React state, and cleans up listeners on unmount — all without memory leaks across navigation.
Filter performance at scale. Hundreds of dealers across dozens of countries, each with multiple product group affiliations. Filtering needs to feel instant. The useDealerFilters hook pre-computes available options and uses useMemo to avoid recalculating the filtered dealer list on every render. Country selection cascades to state options (US only), which cascades to the visible dealer set.
Map-list synchronization. When a user clicks a pin on the map, the corresponding dealer card scrolls into view and highlights. When they click a card, the map pans to that dealer's location and opens the popup. This bidirectional sync runs through a shared activeDealerId state without causing infinite update loops.
Multi-region deployment. The locator serves all 10 Pantone sites across 4 regions and 7 locales. Translation strings, dealer data, and regional website links all vary by locale.
Results
A tool I built from scratch in 2017 that has survived three map provider migrations and a complete frontend architecture rewrite — all while remaining one of the most-used tools on pantone.com. The current Next.js version runs as a clean, typed, componentized system within the modern BigCommerce Catalyst stack.
Tech Stack
Next.js 15, TypeScript, Azure Maps SDK, AWS Lambda, Aurora, React Custom Hooks, Tailwind CSS, next-intl, ISR Caching
