使用react-leaflet与Next.js时如何修复'window is not defined'错误?
修复Next.js中Leaflet的
window is not defined错误 核心思路
确保Leaflet的初始化、导入逻辑完全在客户端环境执行,避免SSR阶段触发依赖window的代码,同时保留根据location更新地图的能力。
步骤1:创建客户端专属地图组件
新建components/ClientMap.js,将所有Leaflet相关逻辑放在仅客户端执行的useEffect中,避免顶层导入导致SSR报错:
import { useEffect, useRef } from 'react'; // 仅在客户端导入Leaflet及样式 let L; if (typeof window !== 'undefined') { L = require('leaflet'); require('leaflet/dist/leaflet.css'); // 修复Leaflet默认图标路径问题(Next.js public目录需放置对应资源) delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: '/leaflet/images/marker-icon-2x.png', iconUrl: '/leaflet/images/marker-icon.png', shadowUrl: '/leaflet/images/marker-shadow.png', }); } export default function ClientMap({ location }) { const mapRef = useRef(null); useEffect(() => { // 确保window存在且Leaflet已加载 if (!window || !L || !mapRef.current) return; let map = mapRef.current._leafletMap; // 初始化地图 if (!map) { map = L.map(mapRef.current).setView([location.lat, location.lng], 13); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); mapRef.current._leafletMap = map; } // 根据传入的location更新地图中心 else { map.setView([location.lat, location.lng], map.getZoom()); } // 组件卸载时清理地图实例 return () => { if (map) map.remove(); }; }, [location]); return <div ref={mapRef} style={{ width: '100%', height: '400px' }} />; }
步骤2:在页面中动态导入组件(带SSR禁用)
在页面组件中使用next/dynamic导入ClientMap,明确设置ssr: false,同时传递location状态(支持useMemo处理后的依赖):
import dynamic from 'next/dynamic'; import { useState, useMemo } from 'react'; // 动态导入客户端地图组件,禁用SSR const ClientMap = dynamic(() => import('../components/ClientMap'), { ssr: false, loading: () => <div>加载地图中...</div> }); export default function MapPage() { const [location, setLocation] = useState({ lat: 51.505, lng: -0.09 }); // 用useMemo处理location,确保依赖变化时触发地图更新 const memoizedLocation = useMemo(() => ({ ...location }), [location]); // 模拟location更新场景 const switchToParis = () => { setLocation({ lat: 48.8566, lng: 2.3522 }); }; return ( <div> <button onClick={switchToParis}>切换到巴黎</button> <ClientMap location={memoizedLocation} /> </div> ); }
步骤3:处理Leaflet静态资源
- 从Leaflet包中复制图标文件:
node_modules/leaflet/dist/images/下的marker-icon-2x.png、marker-icon.png、marker-shadow.png - 在Next.js项目的
public目录下创建leaflet/images文件夹,将复制的图标放入其中
关键注意事项
- 禁止在页面组件或任何SSR执行的代码中顶层导入Leaflet,所有Leaflet相关导入必须放在客户端专属组件内或条件判断中
- 动态导入时必须设置
ssr: false,确保组件完全在客户端渲染 - 通过
props传递location状态,配合useEffect的依赖数组,实现地图随location更新的逻辑
内容的提问来源于stack exchange,提问作者Ahmad Nasser
相关产品推荐
相关产品推荐

