0

Im using Next.js 14.1.0 version and im using leaflet map in my project in two custom hook and i got this error in my development running console.

⨯ node_modules\leaflet\dist\leaflet-src.js (230:18) @ window ⨯ ReferenceError: window is not defined at webpack_require (H:\Web_Design\Next.js\Kashan_Yab.next\server\webpack-runtime.js:33:43) at eval (./hooks/use-custom-map.tsx:12:65) at (ssr)/./hooks/use-custom-map.tsx (H:\Web_Design\Next.js\Kashan_Yab.next\server\app(main-layout)(footer-layout)\ads\page.js:1125:1) at webpack_require (H:\Web_Design\Next.js\Kashan_Yab.next\server\webpack-runtime.js:33:43) at eval (./hooks/use-leaflet-map.tsx:11:73) at (ssr)/./hooks/use-leaflet-map.tsx (H:\Web_Design\Next.js\Kashan_Yab.next\server\app(main-layout)(footer-layout)\ads\page.js:1191:1) at webpack_require (H:\Web_Design\Next.js\Kashan_Yab.next\server\webpack-runtime.js:33:43) at eval (./hooks/index.ts:20:74) at (ssr)/./hooks/index.ts (H:\Web_Design\Next.js\Kashan_Yab.next\server\app(main-layout)(footer-layout)\ads\page.js:1103:1) at webpack_require (H:\Web_Design\Next.js\Kashan_Yab.next\server\webpack-runtime.js:33:43) at eval (./services/businesses/filter/GetBusinesses.ts:6:64) at (ssr)/./services/businesses/filter/GetBusinesses.ts (H:\Web_Design\Next.js\Kashan_Yab.next\server\app(main-layout)(footer-layout)\ads\page.js:1466:1) at webpack_require (H:\Web_Design\Next.js\Kashan_Yab.next\server\webpack-runtime.js:33:43) at eval (./services/businesses/index.ts:26:80) at (ssr)/./services/businesses/index.ts (H:\Web_Design\Next.js\Kashan_Yab.next\server\app(main-layout)(footer-layout)\ads\page.js:1521:1) at webpack_require (H:\Web_Design\Next.js\Kashan_Yab.next\server\webpack-runtime.js:33:43) at eval (./store/archive-store.ts:9:78) at (ssr)/./store/archive-store.ts (H:\Web_Design\Next.js\Kashan_Yab.next\server\app(main-layout)(footer-layout)\ads\page.js:1686:1) at webpack_require (H:\Web_Design\Next.js\Kashan_Yab.next\server\webpack-runtime.js:33:43) at eval (./store/index.ts:9:72) at (ssr)/./store/index.ts (H:\Web_Design\Next.js\Kashan_Yab.next\server\app(main-layout)(footer-layout)\ads\page.js:1708:1) at webpack_require (H:\Web_Design\Next.js\Kashan_Yab.next\server\webpack-runtime.js:33:43) at eval (./components/providers/AppProvider.tsx:10:64) at (ssr)/./components/providers/AppProvider.tsx (H:\Web_Design\Next.js\Kashan_Yab.next\server\app(main-layout)(footer-layout)\ads\page.js:553:1) at webpack_require (H:\Web_Design\Next.js\Kashan_Yab.next\server\webpack-runtime.js:33:43) null

use-custom-map.tsx

"use client";
import { Icon } from "leaflet";
import { MutableRefObject, useRef, useState } from "react";
import {
    MapContainer,
    MapContainerProps,
    Marker,
    TileLayer,
    useMap,
    useMapEvents,
} from "react-leaflet";

import { Each } from "@/components/utils/Each";

import "leaflet/dist/leaflet.css";

const ClickHandler = ({
    onClick,
}: {
    onClick: (lat: number, lng: number) => void;
}) => {
    useMapEvents({
        click: (e) => {
            const { lat, lng } = e.latlng;
            onClick(lat, lng);
        },
    });

    return null;
};

const GetMap = ({ map }: { map: MutableRefObject<any> }) => {
    map.current = useMap();
    return null;
};

export const mapMarker = new Icon({
    iconUrl: "/images/map/marker-icon.png",
    shadowUrl: "/images/map/marker-shadow.png",
    iconSize: [25, 32],
    iconAnchor: [18, 40],
    shadowAnchor: [17, 50],
});

export const homeMarker = new Icon({
    iconUrl: "/images/map/home.webp",
    shadowUrl: "/images/map/marker-shadow.png",
    iconSize: [50, 50],
    iconAnchor: [23, 50],
    shadowAnchor: [10, 40],
});

export const branchMarker = new Icon({
    iconUrl: "/images/map/shop.webp",
    shadowUrl: "/images/map/marker-shadow.png",
    iconSize: [50, 50],
    iconAnchor: [23, 50],
    shadowAnchor: [10, 40],
});

export const flagMarker = new Icon({
    iconUrl: "/images/map/flag.webp",
    iconSize: [50, 50],
    iconAnchor: [37, 49],
});

export const locationMarker = new Icon({
    iconUrl: "/images/map/location.webp",
    shadowUrl: "/images/map/marker-shadow.png",
    iconSize: [50, 50],
    iconAnchor: [24, 49],
    shadowAnchor: [10, 40],
});

const defaultLocation: LocationInfo = {
    lat: 33.969292153047476,
    lng: 51.40714968697455,
};

const useCustomMap = ({
    mapProps,
    isClickable,
    clickableMarkerInfo,
    markerInfos,
    customMarker,
    markerType,
}: {
    mapProps?: MapContainerProps;
    isClickable?: boolean;
    clickableMarkerInfo?: LocationInfo;
    markerInfos?: LocationInfo[];
    customMarker?: React.ReactNode;
    markerType?: MarkerType;
}) => {
    const map = useRef();
    const mainMarker = markerType
        ? markerType === "Home"
            ? homeMarker
            : markerType === "Branch"
            ? branchMarker
            : markerType === "Flag"
            ? flagMarker
            : locationMarker
        : mapMarker;

    const [info, setInfo] = useState<LocationInfo>(
        clickableMarkerInfo ? clickableMarkerInfo : defaultLocation
    );

    const markersOutput = Each<LocationInfo>({
        of: markerInfos!,
        render: (marker) => (
            <Marker
                position={[marker.lat, marker.lng]}
                icon={mainMarker}
            ></Marker>
        ),
    });

    const mapClickHandler = (lat: number, lng: number) => {
        setInfo({
            lat,
            lng,
        });
    };

    if (map.current) (map.current as any).getCenter();

    const output = (
        <MapContainer
            {...(isClickable && clickableMarkerInfo
                ? { center: [clickableMarkerInfo.lat, clickableMarkerInfo.lng] }
                : markerInfos && markerInfos.length
                ? { center: [markerInfos[0].lat, markerInfos[0].lng] }
                : { center: [defaultLocation.lat, defaultLocation.lng] })}
            zoom={13}
            {...mapProps}
        >
            <TileLayer
                attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
            />
            <GetMap map={map} />
            {isClickable ? <ClickHandler onClick={mapClickHandler} /> : <></>}
            {!isClickable ? (
                customMarker ? (
                    customMarker
                ) : markersOutput !== -1 ? (
                    markersOutput
                ) : (
                    <></>
                )
            ) : (
                <Marker
                    position={[info.lat, info.lng]}
                    icon={mainMarker}
                ></Marker>
            )}
        </MapContainer>
    );

    return { output, map, info };
};

export default useCustomMap;

use-leaflet-map.tsx

"use client";
import { useEffect } from "react";
import { Form, FormInstance, Input } from "antd";

import useCustomMap from "./use-custom-map";
import "leaflet/dist/leaflet.css";

const useLeafletMap = (
    form: FormInstance<any>,
    defaultInfo?: LocationInfo,
    className?: string,
    isInModal: boolean = false,
    markerType?: MarkerType,
) => {
    const {
        output: mapOutput,
        info,
        map,
    } = useCustomMap({
        mapProps: {
            className: `custom-map ${className ? className : ""}`,
        },
        isClickable: true,
        clickableMarkerInfo: defaultInfo
            ? {
                  lat: defaultInfo.lat!,
                  lng: defaultInfo.lng!,
              }
            : undefined,
            markerType
    });

    useEffect(() => {
        if ( typeof window !== "undefined" ) {
            if (isInModal) {
                if (map.current) {
                    (map.current as any).invalidateSize();
                } else {
                    setTimeout(() => {
                        if (map.current) (map.current as any).invalidateSize();
                    }, 500);
                }
            }
    
            form.setFieldsValue({
                lat: info.lat,
                lng: info.lng,
            });
        }
    }, [defaultInfo, map.current, isInModal, form, info]);

    const output = (
        <>
            <Form.Item name="lat">
                <Input type="hidden" />
            </Form.Item>
            <Form.Item name="lng">
                <Input type="hidden" />
            </Form.Item>
            {mapOutput}
        </>
    );

    return output;
};

export default useLeafletMap;

give me a right solution of fixing this error in Next.js

1 Answer 1

0

Traditionally Leaflet does not work with SSR. You would most likely need to dynamic load the components that are using leaflets own components:

// Load every leaflet compnent since they dont work with ssr
interface MapProps extends LeafletMapProps {}

const Map: React.FC<MapProps> = (props) => {
  const Map = React.useMemo(
    () =>
      dynamic(() => import("src/components/Map/Leaflet/LeafletMap"), {
        loading: () => <Skeleton height={"400px"} />,
        ssr: false,
      }),
    []
  );
  return <Map {...props} />;
};

LeafletMap in the example above is a component where I use MapContainer.

I would try it with a simpler component first and tak it in steps. Since sometimes you will need to dynamic import several components.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.