I'm trying to set up server-side rendering for my Vue.js SPA built using Inertia and Laravel. Whenever I try to load a page including a Map, I get an error like the following from the SSR server process:
[Vue warn]: Unhandled error during execution of setup function
at <Map errors= {} venues= [
{
name: 'Some Hall',
lat: '45.492480',
lng: '0.336327',
address: '1 Interesting Street',
url: 'http://localhost/venues/some-hall'
},
**** Lots more prop data elements here ****
}
] key=null >
TypeError: Loader is not a constructor
at setup (file:///var/www/html/bootstrap/ssr/ssr.js:510:20)
at _sfc_main$4.setup (file:///var/www/html/bootstrap/ssr/ssr.js:613:25)
at callWithErrorHandling (/var/www/html/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:156:18)
at setupStatefulComponent (/var/www/html/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7244:25)
at setupComponent (/var/www/html/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7205:36)
at renderComponentVNode (/var/www/html/node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:614:15)
at renderVNode (/var/www/html/node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:743:14)
at renderComponentSubTree (/var/www/html/node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:698:7)
at renderComponentVNode (/var/www/html/node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:631:12)
at renderVNode (/var/www/html/node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:743:14)
The site is using composer packages laravel/framework@10.28.0
and inertiajs/inertia-laravel@0.6.11
. My package.json
looks like this:
{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build && vite build --ssr"
},
"devDependencies": {
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.8.0",
"vite": "^4.0.0"
},
"dependencies": {
"@googlemaps/js-api-loader": "^1.16.2",
"@inertiajs/vue3": "^1.0.12",
"@vitejs/plugin-vue": "^4.4.0",
"@vue/server-renderer": "^3.3.7",
"bootstrap": "^5.3.2",
"vue": "^3.3.6"
}
}
My resources/js/app.js
looks like this:
import {createSSRApp, h} from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
createInertiaApp({
resolve: name => {
const pages = import.meta.glob('./Pages/**/*.vue', { eager: true });
return pages[`./Pages/${name}.vue`];
},
setup({ el, App, props, plugin }) {
createSSRApp({ render: () => h(App, props) })
.use(plugin)
.mount(el);
},
});
And my resources/js/ssr.js
looks like this:
import { createInertiaApp } from '@inertiajs/vue3'
import createServer from '@inertiajs/vue3/server'
import { renderToString } from '@vue/server-renderer'
import { createSSRApp, h } from 'vue'
createServer(page =>
createInertiaApp({
page,
render: renderToString,
resolve: name => {
const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
return pages[`./Pages/${name}.vue`]
},
setup({ App, props, plugin }) {
return createSSRApp({
render: () => h(App, props),
}).use(plugin)
},
}),
)
Searches for answers so far led me down the track of whether this is a CommonJS/ESM compatibility issue (although I don't fully understand this), so when importing the js-api-loader
module, I've tried both of the following:
// Initially this...
import { Loader } from "@googlemaps/js-api-loader";
// And also this...
import * as GMaps from '@googlemaps/js-api-loader'
const { Loader } = GMaps
Neither seems to make any difference. As far as I understand, the second option is to do with handling CJS modules, but I don't think the Google module is using the CommonJS standard, as far as I can tell.
The code in my *.vue
pages that seems to cause the error is:
const loader = new Loader({
apiKey: env.google_maps_api_key,
version: "weekly",
libraries: ['places'],
});
Would really appreciate some guidance!
I put some console.log()
lines in where the error is being reported:
const { Loader } = GMaps;
const props = __props;
console.log(GMaps);
console.log(Loader);
const loader = new Loader({
apiKey: env.google_maps_api_key,
version: "weekly",
libraries: ["places"]
});
The output is:
[Module: null prototype] {
default: {
LoaderStatus: {
'0': 'INITIALIZED',
'1': 'LOADING',
'2': 'SUCCESS',
'3': 'FAILURE',
INITIALIZED: 0,
LOADING: 1,
SUCCESS: 2,
FAILURE: 3
},
DEFAULT_ID: '__googleMapsScriptId',
Loader: [Function: o]
}
}
undefined
Well, I have solved this for now, but I don't really understand what's going on. I'd be happy to change the accepted answer if someone can provide a better explanation of what's going on here in terms of module types, imports, node and the browser.
Essentially, my working import now looks like this:
import * as GMaps from '@googlemaps/js-api-loader';
const { Loader } = GMaps.default ?? GMaps;
It seems that in one instance (the browser, I think?), the module is imported as an object containing the exported properties of the module (i.e. { LoaderStatus: ..., DEFAULT_ID: ..., Loader: ... }
and in another (node on the server, I think) the module is imported wrapped in a parent object's default
property (i.e. { default: { LoaderStatus: ..., DEFAULT_ID: ..., Loader: ... }}
). I'm not sure why.
The solution above essentially uses the ??
operator to first try to deconstruct the default
property, if it exists, and if not, fall back to the deconstructing the parent object itself.