NextJS13集成react-leaflet报Window is not defined,use client无效
在Next.js 13中解决react-leaflet的"window is not defined"错误
问题场景
在Next.js 13中使用react-leaflet组件时,即使在文件顶部添加了'use client'指令,也通过typeof window !== 'undefined'做了环境判断,依然出现"window is not defined"错误。
原页面代码(pages/censusmap.tsx):
'use client'; import { type NextPage } from "next"; import { MapContainer, TileLayer, GeoJSON } from 'react-leaflet'; // TODO: Fix this import to be from geojson and change the filename import CensusTractData from "./data/census-tracts_min.json"; import 'leaflet/dist/leaflet.css'; import type {GeoJsonObject, Feature, Geometry} from "geojson"; const isBrowser = () => typeof window !== 'undefined'; const CensusTractMap: NextPage = () => { const getCensusTractFillColor = (population2020: number) => { if(population2020 <= 2500) { return "#bd93f9" } if(population2020 > 2500 && population2020 < 5000) { return "#ffb86c" } if (population2020 > 5000 && population2020 < 10000) { return "#8be9fd"; } return undefined; } const censusTractStyle = (val: Feature<Geometry, {pop20: number}> | undefined) => { if(!val) { return {}; } const pop20 = Number(val.properties.pop20); return { fillColor: getCensusTractFillColor(pop20), color: '#44475a', weight: 0.5, opacity: 1, fillOpacity: 0.4, } } return ( <> <div id="map" className="h-180px"> <MapContainer center={[20.5, -157.510857]} zoom={7} scrollWheelZoom={true}> <TileLayer attribution='© "Map data © OpenStreetMap contributors, Esri Community Maps contributors, Map layer by Esri' url="https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}" /> <GeoJSON data={CensusTractData as GeoJsonObject} style={(val: Feature<Geometry, {pop20: number}> | undefined) => censusTractStyle(val)}/> </MapContainer> </div> </> ) } export default CensusTractMap
报错信息:
error - ReferenceError: window is not defined at ...\node_modules\leaflet\dist\leaflet-src.js:230:19 at ...\node_modules\leaflet\dist\leaflet-src.js:7:66 at Object.<anonymous> (...\node_modules\leaflet\dist\leaflet-src.js:10:3) at Module._compile (node:internal/modules/cjs/loader:1105:14) at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10) at Module.load (node:internal/modules/cjs/loader:981:32) at Function.Module._load (node:internal/modules/cjs/loader:822:12) at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:170:29) at ModuleJob.run (node:internal/modules/esm/module_job:198:25) at async Promise.all (index 0) at async ESMLoader.import (node:internal/modules/esm/loader:385:24) at async importModuleDynamicallyWrapper (node:internal/vm/module:437:15) { page: '/censusmap'
解决方法
问题根源在于leaflet库在模块导入阶段就直接访问了window对象,而Next.js 13中即使页面加了'use client',模块导入过程仍会在服务端执行,导致报错。需要通过动态导入并禁用SSR来解决:
步骤1:将地图逻辑拆分到单独的客户端组件
创建components/CensusMapClient.tsx,把所有和react-leaflet相关的代码移到这里,确保顶部有'use client':
'use client'; import { MapContainer, TileLayer, GeoJSON } from 'react-leaflet'; import CensusTractData from "../pages/data/census-tracts_min.json"; import 'leaflet/dist/leaflet.css'; import type {GeoJsonObject, Feature, Geometry} from "geojson"; const getCensusTractFillColor = (population2020: number) => { if(population2020 <= 2500) { return "#bd93f9"; } if(population2020 > 2500 && population2020 < 5000) { return "#ffb86c"; } if (population2020 > 5000 && population2020 < 10000) { return "#8be9fd"; } return undefined; }; const censusTractStyle = (val: Feature<Geometry, {pop20: number}> | undefined) => { if(!val) { return {}; } const pop20 = Number(val.properties.pop20); return { fillColor: getCensusTractFillColor(pop20), color: '#44475a', weight: 0.5, opacity: 1, fillOpacity: 0.4, }; }; export default function CensusMapClient() { return ( <div id="map" className="h-180px"> <MapContainer center={[20.5, -157.510857]} zoom={7} scrollWheelZoom={true}> <TileLayer attribution='© "Map data © OpenStreetMap contributors, Esri Community Maps contributors, Map layer by Esri' url="https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}" /> <GeoJSON data={CensusTractData as GeoJsonObject} style={censusTractStyle} /> </MapContainer> </div> ); }
步骤2:在页面中动态导入该客户端组件
修改pages/censusmap.tsx,使用Next.js的dynamic函数导入地图组件,并设置ssr: false,确保组件只在客户端渲染:
import { type NextPage } from "next"; import dynamic from 'next/dynamic'; // 动态导入地图组件,禁用服务端渲染 const CensusMapClient = dynamic(() => import('../components/CensusMapClient'), { ssr: false, loading: () => <div>加载地图中...</div> // 可选:添加加载状态提示 }); const CensusTractMap: NextPage = () => { return ( <> <CensusMapClient /> </> ); }; export default CensusTractMap;
原理说明
通过dynamic导入并设置ssr: false,Next.js会跳过该组件的服务端渲染过程,仅在浏览器环境中加载和执行组件代码,从根源避免了服务端访问window对象的问题。
内容的提问来源于stack exchange,提问作者avenmia
相关产品推荐
相关产品推荐

