Next.js添加'use client'后引入Leaflet仍报window未定义错误
问题:Next.js + Leaflet 引入时出现 "window is not defined" 错误
已在页面添加 "use client" 指令,但开发模式运行仍报错,构建也无法完成,错误信息如下:
ReferenceError: window is not defined at instantiateModule (C:\Users\Aryan\Documents\Web Dev\AutoResQ-Next\autoresq-next\.next\server\chunks\ssr\[turbopack]_runtime.js:590:23) at getOrInstantiateModuleFromParent (C:\Users\Aryan\Documents\Web Dev\AutoResQ-Next\autoresq-next\.next\server\chunks\ssr\[turbopack]_runtime.js:645:12) at esmImport (C:\Users\Aryan\Documents\Web Dev\AutoResQ-Next\autoresq-next\.next\server\chunks\ssr\[turbopack]_runtime.js:132:20) at [project]/src/app/(app)/connect-client-request/[requestId]/page.tsx [app-ssr] (ecmascript) (file://C%3A/Users/Aryan/Documents/Web%20Dev/AutoResQ-Next/autoresq-next/src/app/%28app%29/connect-client-request/%5BrequestId%5D/page.tsx:7:0) at instantiateModule (C:\Users\Aryan\Documents\Web Dev\AutoResQ-Next\autoresq-next\.next\server\chunks\ssr\[turbopack]_runtime.js:590:23) at getOrInstantiateModuleFromParent (C:\Users\Aryan\Documents\Web Dev\AutoResQ-Next\autoresq-next\.next\server\chunks\ssr\[turbopack]_runtime.js:645:12) at commonJsRequire (C:\Users\Aryan\Documents\Web Dev\AutoResQ-Next\autoresq-next\.next\server\chunks\ssr\[turbopack]_runtime.js:147:20) 5 | import { io, Socket } from "socket.io-client"; 6 | import { useSession } from "next-auth/react"; > 7 | import L from "leaflet"; 8 | import "leaflet/dist/leaflet.css"; 9 | import axios from "axios"; 10 | import { useToast } from "@/hooks/use-toast"; { digest: '3246626898' }
页面代码:
"use client"; import React, { useEffect, useRef, useState, useCallback } from "react"; import L from "leaflet"; import "leaflet/dist/leaflet.css"; interface Coords { lat: number; long: number; } const Page: React.FC = () => { const [userCoords, setUserCoords] = useState<Coords | null>(null); const [status, setStatus] = useState<string>("Initializing location..."); const mapRef = useRef<L.Map | null>(null); const markersLayerRef = useRef<L.LayerGroup | null>(null); const markerRef = useRef<L.Marker | null>(null); const watchIdRef = useRef<number | null>(null); const initializeMap = useCallback((coords: Coords) => { // Prevent reinitializing if map already exists if (mapRef.current) { // Update existing marker or map view markerRef.current?.setLatLng([coords.lat, coords.long]); mapRef.current.setView([coords.lat, coords.long], 16); return; } // Create map only once const map = L.map("map", { center: [coords.lat, coords.long], zoom: 16, zoomControl: true, attributionControl: false }); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: "© OpenStreetMap contributors", }).addTo(map); // Create marker const customIcon = L.icon({ iconUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRLWRQ2MP28ucwL3bUexiJ8kfDjKM_IO6TCrw&s", iconSize: [40, 40], iconAnchor: [20, 40], popupAnchor: [0, -40], }); const marker = L.marker([coords.lat, coords.long], { icon: customIcon }).addTo(map); // Store references mapRef.current = map; markerRef.current = marker; }, []); useEffect(() => { // Geolocation tracking if ("geolocation" in navigator) { watchIdRef.current = navigator.geolocation.watchPosition( (position) => { const newCoords: Coords = { lat: position.coords.latitude, long: position.coords.longitude, }; // Minimal state update setUserCoords(prevCoords => { // Only update if coordinates have changed significantly const hasChanged = !prevCoords || Math.abs(newCoords.lat - prevCoords.lat) > 0.0001 || Math.abs(newCoords.long - prevCoords.long) > 0.0001; if (hasChanged) { initializeMap(newCoords); setStatus(`Location: ${newCoords.lat.toFixed(4)}, ${newCoords.long.toFixed(4)}`); return newCoords; } return prevCoords; }); }, (error) => { const errorMessages = { [error.PERMISSION_DENIED]: "Location access denied", [error.POSITION_UNAVAILABLE]: "Location information unavailable", [error.TIMEOUT]: "Location request timed out" }; setStatus(`Error: ${errorMessages[error.code] || "Unknown error"}`); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0, } ); } else { setStatus("Geolocation not supported"); } // Cleanup return () => { if (watchIdRef.current !== null) { navigator.geolocation.clearWatch(watchIdRef.current); } if (mapRef.current) { mapRef.current.remove(); } }; }, [initializeMap]); return ( <div className="w-full bg-white p-6 rounded-lg shadow-lg space-y-4"> <p className="text-lg font-semibold text-gray-700">{status}</p> <div id="map" className="h-[80vh] w-full rounded-lg shadow-md border border-gray-200" /> </div> ); }; export default Page;
解决方案
1. 动态导入Leaflet(推荐)
将静态导入改为动态导入,确保仅在客户端环境加载Leaflet:
修改页面代码中的导入部分,移除静态的import L from "leaflet",改用useEffect配合动态导入:
"use client"; import React, { useEffect, useRef, useState, useCallback } from "react"; import "leaflet/dist/leaflet.css"; // CSS可正常静态导入 interface Coords { lat: number; long: number; } const Page: React.FC = () => { const [L, setL] = useState<typeof import("leaflet") | null>(null); const [userCoords, setUserCoords] = useState<Coords | null>(null); const [status, setStatus] = useState<string>("Initializing location..."); const mapRef = useRef<import("leaflet").Map | null>(null); const markerRef = useRef<import("leaflet").Marker | null>(null); const watchIdRef = useRef<number | null>(null); // 动态加载Leaflet useEffect(() => { const loadLeaflet = async () => { const leaflet = await import("leaflet"); setL(leaflet); }; loadLeaflet(); }, []); const initializeMap = useCallback((coords: Coords) => { if (!L) return; // 确保Leaflet已加载 if (mapRef.current) { markerRef.current?.setLatLng([coords.lat, coords.long]); mapRef.current.setView([coords.lat, coords.long], 16); return; } const map = L.map("map", { center: [coords.lat, coords.long], zoom: 16, zoomControl: true, attributionControl: false }); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: "© OpenStreetMap contributors", }).addTo(map); const customIcon = L.icon({ iconUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRLWRQ2MP28ucwL3bUexiJ8kfDjKM_IO6TCrw&s", iconSize: [40, 40], iconAnchor: [20, 40], popupAnchor: [0, -40], }); const marker = L.marker([coords.lat, coords.long], { icon: customIcon }).addTo(map); mapRef.current = map; markerRef.current = marker; }, [L]); // 其余逻辑保持不变 };
2. 配置Next.js忽略Leaflet的SSR
在next.config.js中添加配置,告知Next.js Leaflet是客户端专属模块,跳过SSR处理:
/** @type {import('next').NextConfig} */ const nextConfig = { webpack: (config) => { config.module.rules.push({ test: /leaflet\.js$/, use: ['null-loader'], }); return config; }, }; module.exports = nextConfig;
执行安装依赖命令:npm install null-loader --save-dev
3. 显式判断客户端环境后导入
使用require替代import,在导入前检查是否处于浏览器环境:
"use client"; import React, { useEffect, useRef, useState, useCallback } from "react"; import "leaflet/dist/leaflet.css"; let L: typeof import("leaflet") | null = null; if (typeof window !== "undefined") { L = require("leaflet"); } // 后续使用L的代码需先判断L是否存在
原因说明
即使添加了"use client",Next.js在构建或开发阶段仍会对客户端组件执行SSR预检查,而Leaflet源码加载时会直接访问window对象,导致SSR阶段报错。动态导入或配置Webpack跳过SSR处理,能确保Leaflet仅在浏览器环境中加载,避免触发window is not defined错误。
内容的提问来源于stack exchange,提问作者Atul Anand Oraon
相关产品推荐
相关产品推荐

