nuxt: External script doesn't always load when added to Nuxt page via "head()"
Version
Reproduction link
https://github.com/markplewis/nuxt-external-resource-loading-issue
Steps to reproduce
Clone my demo repo and then do the following:
Case 1
- Launch the app via
npm run devand open it up in your browser. - Click the “Test 1” link.
- The following error will appear in your console:
TypeError: window.jQuery is not a function
at VueComponent.mounted (test-1.js:34)
- Refresh the page.
- Everything will load correctly.
- Click the “Home” link.
- Click the “Test 1” link.
- Everything will load correctly.
- Click the “Home” link.
- Refresh the page.
- Click the “Test 1” link.
- The following error will appear in your console:
TypeError: window.jQuery is not a function
at VueComponent.mounted (test-1.js:34)
- Refresh the page.
- Everything will load correctly.
Case 2
- Launch the app via
npm run devand open it up in your browser. - Click the “Test 2” link.
- The “(CDN script has loaded)” message will not be appended to the
<h1>element. - Refresh the page.
- The “(CDN script has loaded)” message will appear inside the
<h1>. - Click the “Home” link.
- Click the “Test 2” link.
- The “(CDN script has loaded)” message will appear inside the
<h1>. - Click the “Home” link.
- Refresh the page.
- Click the “Test 2” link.
- The “(CDN script has loaded)” message will not be appended to the
<h1>element. - Refresh the page.
- The “(CDN script has loaded)” message will appear inside the
<h1>.
What is expected ?
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.
What is actually happening?
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
layoutbasis 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).
<div align="right">This bug report is available on Nuxt community (#c8677)</div>About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 26 (1 by maintainers)
Commits related to this issue
- something https://github.com/nuxt/nuxt.js/issues/5052 — committed to staten-island-tech/fullstack-frontend-4-plus-1-frontend by wilsonw13 2 years ago
Thanks @rchl. This is the simplest example that I could come up with and it seems to be working:
Thanks to vue-meta, this should be way easier now. I’ve wrote a bit about my preferred way in this article.
@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 inmountedhook already. That’s false assumption as scripts appended dynamically don’t load in sync fashion. You have to listen toloadevent to know when they have loaded and only then you can start using code that it defines.In
Case 2is 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 timemountedis 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
headfunction, you need to use something likeVueScript2to 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).Looks good to me. Something like
VueScript2would abstract that and make it look a bit nicer IMO but this is nice, no-lib solution.BTW.
mountedis only executed on the client so check for!process.serveris not needed.This solve my issue: https://vue-meta.nuxtjs.org/api/#callback
You only need to ensure to add
vmidand set thecallbackOps. Sorry, I got totally confused. Yes, Nuxt used
hid, notvmid.replace
vmidwithhidmight solve the problemNuxt usesvmid, nothid. If you are gonna rename it tohidit will likely just act like neithervmidorhidare set, which has its own implications.EDIT: It’s the opposite.
This works for me, but not when loading the page for the first time. It works only when navigating to the page.
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 <script> to <head> if the page is requested directly, not as a route-view component.
I also faced this issue trying to load the Stripe API (v3) in a page, often it’ll say Stripe library isn’t loaded.