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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 22:04:55