I'm attempting to deploy a simple web app built with ArcGIS Javascript API v4.23. It's a React app that displays a single Map within a single MapView.
The problem I'm having is that the basemap tiles don't render when the app is deployed as a Docker container. Has anyone else seen this?
I can launch the app via localhost:3000 and everything works as expected. The map renders within the correct div and it shows the gray canvas basemap tiles. The CSS is also loading correctly and the zoom +/- buttons render correctly. All of this correct behavior happens when the app is launched locally. I can deploy it IIS or I can just run 'npm start' in VS Code and it runs without issues. This is how it displays:
Code within the React app that creates the map and map view:
import logo from './logo.svg';
import './App.css';
import { useEffect, useState, useRef } from "react";
import esriConfig from "@arcgis/core/config";
import Map from "@arcgis/core/Map";
import MapView from "@arcgis/core/views/MapView";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
function App() {
const mapRef = useRef();
esriConfig.apiKey =
"removed";
const initMap = () => {
const map = new Map({
basemap: "arcgis-light-gray",
layers: []
});
const view = new MapView({
map: map,
center: [-82.354, 27.895],
zoom: 9,
container: mapRef.current,
});
};
useEffect(() => {
initMap();
}, []);
return (
<div className="App">
<div id="map-div" ref={mapRef}>
</div>
</div>
);
}
export default App;
All of the above works when hosted locally or build and deployed to IIS.
But the ultimate goal is to deploy this app within a Docker container. The image builds successfully and also runs successfully, but when I browse the app running inside the container the basemap tiles don't render.
I get the same result in Chrome and Edge. There are no errors or exception thrown in the browser's console. It's as if the JS API is loading successfully but somehow the image tiles aren't rendering and I can't see any reason why this would fail only inside a Docker container.
Dockerfile
FROM node:latest as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:latest
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/build/ /usr/share/nginx/html/
.dockerignore file
node_modules
Dockerfile
.git
default.conf for NGINX
server {
listen 80;
listen [::]:80;
root /usr/share/nginx/html;
index index.html index.htm index.nginx-debian.html;
server_name localhost;
location / {
try_files $uri $uri/ =404;
}
}
I believe the problem has to have something to do with NGINX and how it is failing to handle the image tiles correctly, but that's just a half-baked theory.
Update...
I noticed some differences in the requests being made by each version of this app. In the network tab of Chrome's developer tools it looks like the app makes an extra call when running locally that is not made when it's running in a docker container.
Locally it makes two calls to LightGray:Base and LightGray:Labels, and also World_Basemap_v2 further down.
I see fewer calls for the light gray basemap resource in the Docker container and no request for World_Basemap_v2 at all.
Any help is appreciated. Thanks.
Solved! Go to Solution.
I found two work-arounds.
The problem seems to be with the esriConfig module, specifically defining an API key within a React app. A key is required to use the premium set of basemaps which includes the one I'm using, "arcgis-light-gray". I have always defined my developer API key in my ArcGIS JS apps and I've done so in this React app as well. But this module appears to be broken in React. Either that or I need to reference the module differently than I currently am. If so, it's not covered in any documentation I could find.
Work-around #1 - use a non-premium basemap like "gray" or "gray-vector". These basemaps lack the same level of detail at higher zoom levels, making this approach unacceptable if you want to zoom in to larger scales and see detail on individual parcels.
Work-around #2 - uninstall @arcgis/core and install esri-loader instead. This set of modules appears to be the better way to use the ArcGIS JS API in React. With esri-loader the modules are imported with the loadModules function instead of require or import. After refactoring the app to use esri-loader I am now able to define my API key and the premium "arcgis-light-gray" basemap renders correctly.
import logo from "./logo.svg";
import "./App.css";
import { loadModules } from "esri-loader";
function App() {
loadModules(['esri/config', 'esri/views/MapView', 'esri/Map']).then(([esriConfig, MapView, Map]) => {
esriConfig.apiKey = "REMOVED";
const map = new Map({
basemap: 'arcgis-light-gray'
});
const mapView = new MapView({
map: map,
zoom: 10,
center: [-82.5, 28],
container: "map-div"
});
});
return (
<div className="App">
<div id="map-div"></div>
</div>
);
}
export default App;
I found two work-arounds.
The problem seems to be with the esriConfig module, specifically defining an API key within a React app. A key is required to use the premium set of basemaps which includes the one I'm using, "arcgis-light-gray". I have always defined my developer API key in my ArcGIS JS apps and I've done so in this React app as well. But this module appears to be broken in React. Either that or I need to reference the module differently than I currently am. If so, it's not covered in any documentation I could find.
Work-around #1 - use a non-premium basemap like "gray" or "gray-vector". These basemaps lack the same level of detail at higher zoom levels, making this approach unacceptable if you want to zoom in to larger scales and see detail on individual parcels.
Work-around #2 - uninstall @arcgis/core and install esri-loader instead. This set of modules appears to be the better way to use the ArcGIS JS API in React. With esri-loader the modules are imported with the loadModules function instead of require or import. After refactoring the app to use esri-loader I am now able to define my API key and the premium "arcgis-light-gray" basemap renders correctly.
import logo from "./logo.svg";
import "./App.css";
import { loadModules } from "esri-loader";
function App() {
loadModules(['esri/config', 'esri/views/MapView', 'esri/Map']).then(([esriConfig, MapView, Map]) => {
esriConfig.apiKey = "REMOVED";
const map = new Map({
basemap: 'arcgis-light-gray'
});
const mapView = new MapView({
map: map,
zoom: 10,
center: [-82.5, 28],
container: "map-div"
});
});
return (
<div className="App">
<div id="map-div"></div>
</div>
);
}
export default App;