How We Removed 145 KB from the Properties Page Bundle
How We Removed 145 KB from the Properties Page Bundle
Release: v0.1.241 · Category: Frontend Performance · Ref: PERF-05
The Problem
Every user who visited the Properties page was silently downloading the entire Leaflet mapping library — approximately 145 KB minified — even if they never clicked to open the map view.
The root cause was a single line in property-map.tsx:
// ❌ Before — inside a useEffect hook
const L = require('leaflet');
Because require() is a CommonJS runtime call, Next.js's bundler cannot statically analyse the import graph at build time. The result: Leaflet ends up inlined into the main client bundle and shipped to every visitor on page load, unconditionally.
A secondary issue: Leaflet's CSS was being loaded from the unpkg.com CDN by injecting a <link> tag into the document head at runtime:
// ❌ Before — CDN link injection
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://unpkg.com/leaflet/dist/leaflet.css';
document.head.appendChild(link);
This created two additional issues:
- External dependency — a CDN outage or network restriction breaks map styling.
- FOUC (Flash of Unstyled Content) — the stylesheet arrives after the map renders, causing a visible style jump.
The Fix
1. Dynamic ESM Import
Replacing require() with a dynamic ESM import() inside the useEffect gives Next.js the information it needs to split Leaflet into its own lazy-loaded chunk:
// ✅ After — dynamic import inside useEffect
useEffect(() => {
const initMap = async () => {
const L = await import('leaflet');
// ... map initialisation
};
initMap();
}, []);
Next.js now emits Leaflet as a separate JavaScript chunk. That chunk is only requested by the browser when PropertyMap is actually mounted — i.e. when the user opens the map view.
2. Self-Hosted CSS Import
Removing the CDN <link> injection and replacing it with a standard static import lets Next.js bundle the stylesheet alongside the component:
// ✅ After — static CSS import at the top of the file
import 'leaflet/dist/leaflet.css';
Because PropertyMap is already wrapped in next/dynamic with ssr: false in properties-list.tsx, this CSS is only injected when the component chunk is loaded — no CDN round-trip, no FOUC.
Why This Works
Next.js performs static analysis of import statements at build time to build a module graph. Dynamic import() expressions are also understood — they produce split points in the bundle. require() calls, by contrast, are opaque to this analysis: the bundler conservatively includes the module in whichever chunk triggers the require().
The existing architecture already anticipated lazy loading: properties-list.tsx wraps PropertyMap with next/dynamic / React.lazy. The fix in this release completes the picture by ensuring Leaflet itself also participates in that laziness.
Impact
| Before | After | |
|---|---|---|
| Leaflet in initial JS bundle | ~145 KB (minified) | 0 KB |
| Leaflet loaded when map opens | ✅ | ✅ |
| CSS source | unpkg.com (external) | Self-hosted |
| FOUC on map render | Possible | Eliminated |
| External CDN dependency | Yes | No |
Files Changed
src/app/dashboard/properties/property-map.tsx