PaulLeCam / react-leaflet

React components for Leaflet maps
https://react-leaflet.js.org
Other
5.19k stars 887 forks source link

Map <Marker> image does not appear on Next.js #808

Closed MathisBarre closed 3 years ago

MathisBarre commented 3 years ago

Expected behavior

When I import my leaflet map in react with dynamic import (required with Next.js), I want the marker images to be display.

Actual behavior

Instead of expected behavior, the marker icon doesn't appear. When I inspect the images with devtools here is the src I get for the image : http://localhost:3000/_next/static/media/marker-icon.2b3e1faf89f94a4835397e7a43b4f77d.png")marker-icon.png

Here is my actuel dependencies:

 "dependencies": {
    "@mdx-js/loader": "^1.6.21",
    "@next/mdx": "^10.0.2",
    "@tailwindcss/typography": "^0.3.1",
    "ackee-tracker": "^4.2.0",
    "autoprefixer": "^10.0.2",
    "dayjs": "^1.9.6",
    "leaflet": "^1.7.1",
    "next": "10.0.3",
    "postcss": "^8.1.9",
    "postcss-flexbugs-fixes": "^5.0.2",
    "postcss-preset-env": "^6.7.0",
    "react": "17.0.1",
    "react-dom": "17.0.1",
    "react-leaflet": "^3.0.4",
    "react-select": "^3.1.1",
    "sass": "^1.29.0",
    "tailwindcss": "^2.0.2"
  },

I've tried this fix found in another old github issue :

import L from 'leaflet';

delete L.Icon.Default.prototype._getIconUrl;

L.Icon.Default.mergeOptions({
    iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
    iconUrl: require('leaflet/dist/images/marker-icon.png'),
    shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});

But unfortunately, it result to a compile error :

./node_modules/leaflet/dist/images/marker-shadow.png 1:0
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)

Steps to reproduce

You can clone this repository to reproduce the issue easily : https://github.com/MathisBarre/leaflet-icon-issue Otherwise you just need to dynamically import a leaflet map in nextjs

kud commented 3 years ago

Same here.

kunKun-tx commented 3 years ago

https://github.com/ghybs/leaflet-defaulticon-compatibility this one seems fix the issue for me.

kud commented 3 years ago

Interesting, thanks

For the moment I did this:

<Marker
          position={[51.505, -0.09]}
          icon={L.divIcon({
            iconSize: [size, size],
            iconAnchor: [size / 2, size + 9],
            className: "mymarker",
            html: "😁",
          })}
        >
MathisBarre commented 3 years ago

https://github.com/ghybs/leaflet-defaulticon-compatibility this one seems fix the issue for me.

Looks like it's working well ! Thanks !

antoinerousseau commented 3 years ago

No need for extra libs!

Simply create a PNG icon (64x64px in my case), put it in /public, and then create an icon instance:

import { icon } from "leaflet"

const ICON = icon({
  iconUrl: "/marker.png",
  iconSize: [32, 32],
})

and use it like this:

<Marker icon={ICON} position={...} />
kud commented 3 years ago

Yessssss, this is better than using the global variable L

silviuburceadev commented 3 years ago

The workarounds are nice, but for default behavior, i.e. showing the default Leaflet marker icon, it should work out of the box. Since we're talking about React here, which most of us use with webpack (via CRA), it should work to just import the CSS from app code, not imported from a CDN (as in the setup guide code) or at least put a warning in the docs, saying that Leaflet CSS doesn't play nice with webpack and you have to do this and that.

antoinerousseau commented 3 years ago

I just import "leaflet/dist/leaflet.css" but yeah for the marker a working default would be nice

jhackett1 commented 3 years ago

this seems to still be broken :(

MathisBarre commented 3 years ago

this seems to still be broken :(

That's true but the workaround are still valid

kud commented 3 years ago

Yeah https://github.com/PaulLeCam/react-leaflet/issues/808#issuecomment-747719927 is really cool. :)

You can create an intermediary component which injects this ICON.

meglio commented 3 years ago

Yeah #808 (comment) is really cool. :)

You can create an intermediary component which injects this ICON.

I tried to create an intermediary component:

import {divIcon, Marker} from 'leaflet';

const ICON = divIcon();

export default function DivIconMarker(props) {

    return <Marker icon={ICON} {...props} />
}

Loading this component dynamically into a Next.js page component produces an error:

image
ted-piotrowski commented 3 years ago

If you're using create-react-app, this will fix broken Marker images:

import L from 'leaflet';
import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';
import markerIcon from 'leaflet/dist/images/marker-icon.png';
import markerShadow from 'leaflet/dist/images/marker-shadow.png';

delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
    iconUrl: markerIcon,
    iconRetinaUrl: markerIcon2x,
    shadowUrl: markerShadow,
})

If you're curious why the old fix at the start of this thread does not work, it is because the default Webpack file-loader configuration (which is used by create-react-app) does not allow using require() for loading files.

I've tried this fix found in another old github issue :

import L from 'leaflet';

delete L.Icon.Default.prototype._getIconUrl;

L.Icon.Default.mergeOptions({
    iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
    iconUrl: require('leaflet/dist/images/marker-icon.png'),
    shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});
DarkSuniuM commented 3 years ago

If you're using create-react-app, this will fix broken Marker images:

import L from 'leaflet';
import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';
import markerIcon from 'leaflet/dist/images/marker-icon.png';
import markerShadow from 'leaflet/dist/images/marker-shadow.png';

delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
    iconUrl: markerIcon,
    iconRetinaUrl: markerIcon2x,
    shadowUrl: markerShadow,
})

If you're curious why the old fix at the start of this thread does not work, it is because the default Webpack file-loader configuration (which is used by create-react-app) does not allow using require() for loading files.

I've tried this fix found in another old github issue :

import L from 'leaflet';

delete L.Icon.Default.prototype._getIconUrl;

L.Icon.Default.mergeOptions({
    iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
    iconUrl: require('leaflet/dist/images/marker-icon.png'),
    shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});

Awesome, using NextJS and create-next-app and it works perfectly fine, Only needs the src of the imported images instead of the object.

import L from 'leaflet';
import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';
import markerIcon from 'leaflet/dist/images/marker-icon.png';
import markerShadow from 'leaflet/dist/images/marker-shadow.png';

delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
    iconUrl: markerIcon.src,
    iconRetinaUrl: markerIcon2x.src,
    shadowUrl: markerShadow.src,
})
fasiha commented 2 years ago

Awesome, using NextJS and create-next-app and it works perfectly fine

Hmm, @DarkSuniuM's solution didn't work for me, using Next.js: browser was getting 404 on http://localhost:3000/[object Module]. I'm not sure if Next or Leaftlet (or npm or something else) is breaking it.

My solution was, instead of any code changes, just to put the files it's looking for in a place where Next can find them: mkdir public && cp node_modules/leaflet/dist/images/* public.

BranislavLazic commented 1 year ago

No need for extra libs!

Simply create a PNG icon (64x64px in my case), put it in /public, and then create an icon instance:

import { icon } from "leaflet"

const ICON = icon({
  iconUrl: "/marker.png",
  iconSize: [32, 32],
})

and use it like this:

<Marker icon={ICON} position={...} />

Yes, there is. Although this seems like a good solution, I noticed that the marker doesn't stay pinned in the same location as I zoom in or zoom out the map. https://github.com/ghybs/leaflet-defaulticon-compatibility fixes this issue.

mm0hammadi commented 1 year ago

No need for extra libs! Simply create a PNG icon (64x64px in my case), put it in /public, and then create an icon instance:

import { icon } from "leaflet"

const ICON = icon({
  iconUrl: "/marker.png",
  iconSize: [32, 32],
})

and use it like this:

<Marker icon={ICON} position={...} />

Yes, there is. Although this seems like a good solution, I noticed that the marker doesn't stay pinned in the same location as I zoom in or zoom out the map. https://github.com/ghybs/leaflet-defaulticon-compatibility fixes this issue.

thanks

CasualEngineerZombie commented 1 year ago

Hello gorgeous people of the internet,

I found this issue and was able to fix it by customizing the marker icon to point to the correct URL.

Here is the code I used:

import { MapContainer, TileLayer, Marker, Popup, Circle, ZoomControl } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import markerIcon from "leaflet/dist/images/marker-icon.png";
import markerIcon2x from "leaflet/dist/images/marker-icon-2x.png";
import markerShadow from "leaflet/dist/images/marker-shadow.png";

const customMarkerIcon = L.icon({
  iconUrl: markerIcon,
  iconRetinaUrl: markerIcon2x,
  shadowUrl: markerShadow,
  iconSize: [25, 41],
  iconAnchor: [12, 41],
  popupAnchor: [1, -34],
  tooltipAnchor: [16, -28],
  shadowSize: [41, 41],
});

const Map = () => {
  return (
    <MapContainer
      className="mapContainer"
      center={[51.505, -0.09]}
      zoom={16}
      scrollWheelZoom={false}
      zoomControl={false}
    >
      <TileLayer
        className="map"
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[51.505, -0.09]} icon={customMarkerIcon}>

      </Marker>
      <Circle
        center={[51.505, -0.09]}
        pathOptions={{ color: "#50C878", fillColor: "#72FE9F" }}
        radius={200}
      />

    </MapContainer>
  );
};

export default Map;

I hope this helps!

Abbosbek-cloud commented 1 year ago

Hello gorgeous people of the internet,

I found this issue and was able to fix it by customizing the marker icon to point to the correct URL.

Here is the code I used:

import { MapContainer, TileLayer, Marker, Popup, Circle, ZoomControl } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import markerIcon from "leaflet/dist/images/marker-icon.png";
import markerIcon2x from "leaflet/dist/images/marker-icon-2x.png";
import markerShadow from "leaflet/dist/images/marker-shadow.png";

const customMarkerIcon = L.icon({
  iconUrl: markerIcon,
  iconRetinaUrl: markerIcon2x,
  shadowUrl: markerShadow,
  iconSize: [25, 41],
  iconAnchor: [12, 41],
  popupAnchor: [1, -34],
  tooltipAnchor: [16, -28],
  shadowSize: [41, 41],
});

const Map = () => {
  return (
    <MapContainer
      className="mapContainer"
      center={[51.505, -0.09]}
      zoom={16}
      scrollWheelZoom={false}
      zoomControl={false}
    >
      <TileLayer
        className="map"
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[51.505, -0.09]} icon={customMarkerIcon}>

      </Marker>
      <Circle
        center={[51.505, -0.09]}
        pathOptions={{ color: "#50C878", fillColor: "#72FE9F" }}
        radius={200}
      />

    </MapContainer>
  );
};

export default Map;

I hope this helps!

Hello @CodePhilanthropist!

I can set it as you did. But I need to change the marker icon of the react-leaflet-geoman-v2.

Lodimup commented 1 year ago

No need for extra libs! Simply create a PNG icon (64x64px in my case), put it in /public, and then create an icon instance:

import { icon } from "leaflet"

const ICON = icon({
  iconUrl: "/marker.png",
  iconSize: [32, 32],
})

and use it like this:

<Marker icon={ICON} position={...} />

Yes, there is. Although this seems like a good solution, I noticed that the marker doesn't stay pinned in the same location as I zoom in or zoom out the map. https://github.com/ghybs/leaflet-defaulticon-compatibility fixes this issue.

I'm using this lib https://github.com/holytrips/react-leaflet-marker to use react component as marker. I got the same problem, when zoomed marker just move around. The repo doesn't fix my problem. I wonder if I'm doing something wrong. Does react-leaflet-marker work on yours?

catalin-axsys-FE commented 10 months ago

In nextJs 14.1.0 I have managed to add a custom marker image by doing the follwoing:

import MapMarkerIcon from "./_assets/some_icon.png";

const customIcon = new L.Icon({
  iconUrl: MapMarkerIcon.src,
  iconSize: [15, 15],
});

.
..
...
<Marker
  key={id}
  icon={customIcon} 
/>
Nnenna-udefi commented 6 months ago

This worked for me. I'm using creact react app and typescript

import markerIcon from "leaflet/dist/images/marker-icon.png";
import markerShadow from "leaflet/dist/images/marker-shadow.png";

(delete (L.Icon.Default.prototype as any)._getIconUrl);

(L.Icon.Default.prototype as any)._getIconUrl = function (name: string): string {
    return require('leaflet/dist/images/' + (name === 'icon' ? 'marker-icon' : 'marker-shadow') + '.png');
}; 
let DefaultIcon = L.icon({
    iconUrl: icon,
    shadowUrl: iconShadow,
});
DIEGOHORVATTI commented 5 months ago

Custom Marker Icon

 const sizeMarkerIcon = 50

const IconMarker = icon({
    iconUrl: `data:image/svg+xml;utf8,${encodeURIComponent(`<?xml version="1.0" encoding="iso-8859-1"?>
    <svg xmlns="http://www.w3.org/2000/svg" width="0.67em" height="1em" viewBox="0 0 1024 1536">
      <path fill="${theme.palette.primary.main}" d="M768 512q0-106-75-181t-181-75t-181 75t-75 181t75 181t181 75t181-75t75-181m256 0q0 109-33 179l-364 774q-16 33-47.5 52t-67.5 19t-67.5-19t-46.5-52L33 691Q0 621 0 512q0-212 150-362T512 0t362 150t150 362" />
    </svg>  
  `)}`,
    iconSize: [sizeMarkerIcon, sizeMarkerIcon],
    iconAnchor: [sizeMarkerIcon / 2, sizeMarkerIcon],
    popupAnchor: [0, -sizeMarkerIcon]
  })
<Marker icon={IconMarker} position={position} draggable={false}>
   <Popup>{descriptionCurrentPosition}</Popup>
</Marker>
image