Using Cesium npm package with Nuxt 3

Hi all. I’ve searched high and low for an answer to this, but it seems like most folks are relying on the (awesome) vue-cesium package these days.

I want to use CesiumJS from an npm package with my Nuxt 3 app, but it seems like Cesium isn’t finding its assets in node_modules. Here’s my trivial CesiumViewer.vue component:

<template>
  <div id="cesiumContainer"></div>
</template>

<script setup lang="ts">
import { ImageryLayer, Ion, OpenStreetMapImageryProvider, Terrain, Viewer } from "cesium";
import "cesium/Build/Cesium/Widgets/widgets.css";
</script>

<script lang="ts">
export default {
  mounted() {
    Ion.defaultAccessToken = '<my Cesium Ion token>'

    let viewer = new Viewer("cesiumContainer", {
      terrain: Terrain.fromWorldTerrain(),
      baseLayerPicker: false,
      animation: true,
      shouldAnimate: true,
      baseLayer: new ImageryLayer(new OpenStreetMapImageryProvider({
        url: "https://tile.openstreetmap.org/"
      })),
    });
  }
}
</script>

My nuxt.config.js is completely vanilla:

export default defineNuxtConfig({
    ssr: false,
    app: {
      baseURL: '/',
    },
})

And my package.json is as well:

{
    "name": "nuxt3-cesium-test",
    "private": true,
    "type": "module",
    "scripts": {
        "dev": "nuxt dev"
    },
    "devDependencies": {
        "nuxt": "^3.8.2",
        "vue": "^3.3.10",
        "vue-router": "^4.2.5"
    },
    "dependencies": {
        "cesium": "^1.113.0"
    }
}

When I try to run this the Cesium UI is displayed, but there’s no globe. And I get these errors in the console:

VM2444:1 Uncaught (in promise) SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
    at JSON.parse (<anonymous>)
    at cesium.js?v=641800c9:15846:17
(anonymous) @ cesium.js?v=641800c9:15846
Promise.then (async)
ApproximateTerrainHeights.initialize @ cesium.js?v=641800c9:16605
GroundPrimitive.initializeTerrainHeights @ cesium.js?v=641800c9:123416
DataSourceDisplay @ cesium.js?v=641800c9:202019
Viewer @ cesium.js?v=641800c9:245759
mounted @ CesiumViewer.vue:16
(anonymous) @ vue.js?v=8c90a48a:4320
callWithErrorHandling @ vue.js?v=8c90a48a:1652
callWithAsyncErrorHandling @ vue.js?v=8c90a48a:1660
hook.__weh.hook.__weh @ vue.js?v=8c90a48a:4300
flushPostFlushCbs @ vue.js?v=8c90a48a:1827
render2 @ vue.js?v=8c90a48a:8107
mount @ vue.js?v=8c90a48a:5368
app.mount @ vue.js?v=8c90a48a:11059
initApp @ entry.js:54
await in initApp (async)
(anonymous) @ entry.js:64

and…

localhost/:1 Uncaught (in promise) DOMException: The source image could not be decoded.
Promise.then (async)
SkyBox.update @ cesium.js?v=641800c9:119354
Scene.updateEnvironment @ cesium.js?v=641800c9:118367
render @ cesium.js?v=641800c9:118774
tryAndCatchError @ cesium.js?v=641800c9:118789
Scene.render @ cesium.js?v=641800c9:118841
CesiumWidget.render @ cesium.js?v=641800c9:120241
render2 @ cesium.js?v=641800c9:119664
requestAnimationFrame (async)
startRenderLoop @ cesium.js?v=641800c9:119688
set @ cesium.js?v=641800c9:120080
CesiumWidget @ cesium.js?v=641800c9:119877
Viewer @ cesium.js?v=641800c9:245726
mounted @ CesiumViewer.vue:16
(anonymous) @ vue.js?v=8c90a48a:4320
callWithErrorHandling @ vue.js?v=8c90a48a:1652
callWithAsyncErrorHandling @ vue.js?v=8c90a48a:1660
hook.__weh.hook.__weh @ vue.js?v=8c90a48a:4300
flushPostFlushCbs @ vue.js?v=8c90a48a:1827
render2 @ vue.js?v=8c90a48a:8107
mount @ vue.js?v=8c90a48a:5368
app.mount @ vue.js?v=8c90a48a:11059
initApp @ entry.js:54
await in initApp (async)
(anonymous) @ entry.js:64

I’m sure I’m missing something obvious here as far as pointing Cesium at its assets, but I’m completely missing it.

Thanks so much for any help!

@Jacques27, it seems that you’ve been down this road. Were you trying to do the same thing? It looks like you made it further than I am.

Hi Toby,

I have indeed and it’s a bit tedious to be honest and it was a lot of trial and error at first but I’ve built multiple projects with it already.

One thing you should probably add is a script to initialise the base URL instead of adding it to the nuxt.config.

export default function setupCesiumBaseUrl() {
const cesiumBaseUrl = ‘./lib/cesium’;
window.CESIUM_BASE_URL = cesiumBaseUrl;
}

I’m calling this function right after imports in my app.vue.

I have also added a script that copies the necessary folders from the node_modules when you build your app (requires packages “del” and “recursive-copy” to be installed:

import copy from ‘recursive-copy’;
import { deleteSync } from ‘del’;

const baseDir = ‘node_modules/cesium/Build/CesiumUnminified’;

const targets = [‘Assets//*', 'ThirdParty//', 'Widgets/**/’, ‘Workers/**/*’, ‘Cesium.js’];

deleteSync(targets.map((src) => .output/public/lib/cesium/${src}));

copy(baseDir, ‘.output/public/lib/cesium’, {
expand: true,
overwrite: true,
filter: targets,
});

In order to get it running in dev mode instead of building it every time, I’ve also added a “lib” folder at the project root which includes the same as what the script above copies. Below is what my folder structure looks like.

Note: My latest cesium project is running with nuxt 3.6.5 and I have not tried this with newer versions of nuxt yet.

Sorry for the short answer, not much time currently, hope it helps.

Best,
Jacques

1 Like

Thank you @Jacques27! That does indeed seem pretty tedious, but I’ll give it a try.