You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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静态资源

  1. 从Leaflet包中复制图标文件:node_modules/leaflet/dist/images/下的marker-icon-2x.png、marker-icon.png、marker-shadow.png
  2. 在Next.js项目的public目录下创建leaflet/images文件夹,将复制的图标放入其中

关键注意事项

  • 禁止在页面组件或任何SSR执行的代码中顶层导入Leaflet,所有Leaflet相关导入必须放在客户端专属组件内或条件判断中
  • 动态导入时必须设置ssr: false,确保组件完全在客户端渲染
  • 通过props传递location状态,配合useEffect的依赖数组,实现地图随location更新的逻辑

内容的提问来源于stack exchange,提问作者Ahmad Nasser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 06:37:16