https://github.com/markplewis/nuxt-external-resource-loading-issue
Clone my demo repo and then do the following:
npm run dev
and open it up in your browser.TypeError: window.jQuery is not a function
at VueComponent.mounted (test-1.js:34)
TypeError: window.jQuery is not a function
at VueComponent.mounted (test-1.js:34)
npm run dev
and open it up in your browser.<h1>
element.<h1>
.<h1>
.<h1>
element.<h1>
.I'm trying to load an external script into a Nuxt page via the page's head
:
head() {
return {
script: [{
src: "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.slim.min.js"
}]
};
}
I expect the script to load and be available every time the page loads, regardless of whether it was served directly from the server or rendered on the client-side.
Unfortunately, the external script doesn't seem to load when the page is server-rendered - it only seems to work when the page is client-rendered. I discovered the following issues, but neither of them have helped me solve this problem:
In that first issue, manniL said the following:
"Loading external scripts through the head function works fine for me. I'd suggest to do this on a
layout
basis anyway. For pages there is the caveat that you can't ensure the readiness easily."
What does "you can't ensure the readiness easily" mean? I'd prefer not to move this script into my layout because it will only be needed for one particular page, and I don't want to force users to download unnecessary resources.
I can avoid errors (see test case 1, above) by wrapping my calls to window.jQuery
, like this:
mounted() {
if (!process.server && window.jQuery) {
...
}
}
But this leaves me with the problem that I described in test case 2 (see above).
Thanks for your contribution to Nuxt.js!
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
If you would like this issue to remain open:
Issues that are labeled as 馃晲Pending
will not be automatically marked as stale.
I also faced this issue trying to load the Stripe API (v3) in a page, often it'll say Stripe library isn't loaded.
Apologies for not replying in time to prevent the bot from closing this issue. I just updated my test repo to Nuxt 2.6.1 and I'm still experiencing the problems described above.
I'm having similar issue when trying to load Youtube API on one particular page.
I've also tried using npm vue-script2 to have more control over external scripts loading.
on mounted() of the page
VueScript2.load('https://www.youtube.com/iframe_api').then(() => {
console.log(new YT.Player('ytplayer')); (YT is the global object inserted from the API)
});
getting the below error
Uncaught (in promise) TypeError: YT.Player is not a constructor
It could technically be _solved_ if I load the script across all pages. However, I don't want user to download unnecessarily.
Does anyone have a solution to this problem?
Thanks for your contribution to Nuxt.js!
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
If you would like this issue to remain open:
Issues that are labeled as pending
will not be automatically marked as stale.
I have the same problem with Stripe and Google maps :p, anyone have a solution to this problem ?
Is it due to nuxt specific version ?
Nuxt version: 2.6.2
Example to reproduce: pages/toto.vue
script: [
{ src: 'https://js.stripe.com/v3/', defer: true },
{
src: `https://maps.googleapis.com/maps/api/js?key=${googleApiKey}&libraries=places`
}
],
@DidelotK All versions are affected, AFAIK.
@markplewis and others: It all works as expected and has to do with how html/JS works, not Nuxt.
In Case 1
, you are navigating in SPA fashion to another page and that adds a script and you expect it to be loaded in mounted
hook already. That's false assumption as scripts appended dynamically don't load in sync fashion. You have to listen to load
event to know when they have loaded and only then you can start using code that it defines.
In Case 2
is a slight variation but pretty much same reason for the behavior. It works on reloading the page as then you'll get the script appended already on the server so it will load synchronously and you'll have jQuery loaded by the time mounted
is fired.
Basically you have to understand the difference between script added programatically (through DOM) and script that is in HTML of the page already. Former will load asynchronously while latter will load synchronously.
So to make this work consistently, rather than using head
function, you need to use something like VueScript2
to load script dynamically and wait for it to load. For jQuery this is enough. For other APIs like YouTube you might also need to wait for the API code to tell you that it has loaded because it might have some async initialisation routine that has to run even after script has loaded (refer to APIs documentation how to handle that).
Thanks @rchl. This is the simplest example that I could come up with and it seems to be working:
export default {
mounted() {
if (!process.server && !window.jQuery) {
const script = document.createElement("script");
script.onload = this.onScriptLoaded;
script.type = "text/javascript";
script.src = "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.slim.min.js";
document.head.appendChild(script);
} else {
this.onScriptLoaded();
}
},
methods: {
onScriptLoaded(event = null) {
if (event) {
console.log("Was added");
} else {
console.log("Already existed");
}
console.log(window.jQuery);
window.jQuery("h1").append(` <span>(CDN script has loaded)</span>`);
}
}
}
Looks good to me. Something like VueScript2
would abstract that and make it look a bit nicer IMO but this is nice, no-lib solution.
BTW. mounted
is only executed on the client so check for !process.server
is not needed.
I created this utility function to load external JS file as Nuxt page's head property doesn't work. In my case, It only adds
Most helpful comment
Thanks @rchl. This is the simplest example that I could come up with and it seems to be working: