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.

I managed to get this working without copying files around. You can configure your server to serve the Cesium resources from the npm package directory directly. This section of the Cesium quick start guide has additional details about what needs to be served. Using Nuxt+Vue defaults, this means configuring Nitro to serve the files from the node_modules directory and configuring Cesium to use the right base url to find them:

nuxt.config.ts:

export default defineNuxtConfig({
    app: {
      head: {
        script: [
          {
            //must match the nitro config below for where the files are being served publicly
            children: `window.CESIUM_BASE_URL='_nuxt/Cesium';`,
          },
        ],
      }
    },
    nitro: {
      publicAssets: [ {
        //Nuxt will copy the files here and serve them publicly
        baseURL: '_nuxt/Cesium/Assets',
        dir: '../node_modules/cesium/Build/Cesium/Assets'
      },
      {
        baseURL: '_nuxt/Cesium/Workers',
        dir: '../node_modules/cesium/Build/Cesium/Workers'
      },
      {
        baseURL: '_nuxt/Cesium/ThirdParty',
        dir: '../node_modules/cesium/Build/Cesium/ThirdParty'
      },
      {
        baseURL: '_nuxt/Cesium/Widgets',
        dir: '../node_modules/cesium/Build/Cesium/Widgets'
      },
    ]},
})

component.vue:

import * as Cesium from 'cesium'
import "cesium/Build/Cesium/Widgets/widgets.css";
...

Additionally, I had a problem where using the full cesium package in workers would crash the workers due to an issue with Cesium’s knockout dependency. This happens even though I’m not using anything from the widget package in the worker, but only happens in dev mode (nuxt dev), not a production build (nuxt build && nuxt start). So I used the @cesium/engine package directly in those cases instead. Including both the cesium and @cesium/engine packages isn’t ideal, but I couldn’t come up with a better solution.

worker.js:

import { SampledPositionProperty } from '@cesium/engine'
...
1 Like