Nuxt.js: use external libraries on client-side only

Created on 29 Mar 2017  Â·  19Comments  Â·  Source: nuxt/nuxt.js

How to use external libraries like chart.js which works only on browser.
I need to import Chart from 'chart.js';

but this throws the error Chart is not defined.

I need this only in one page.

This question is available on Nuxt.js community (#c407)

Most helpful comment

@xemoka : Thanks for setting up the example, it's exactly the same use case/setup I have as well.

@shyamchandranmec : Thanks, for suggestions.

At the end, I just require it inside of mounted hook. It disturbs my code style a bit, but it works. :) Thanks guys for suggestions and maintainers for this awesome nuxt project!

Extending on @shyamchandranmec suggestions, this is my setup usually for client side only libraries:

// tool.js - this is just a loader

/* eslint-disable */
function loadJSAsync(e, n, o) {
  const t = document,
    a = "script",
    i = t.createElement(a),
    r = t.getElementsByTagName(a)[0];
  i.src = "//" + e, n && i.addEventListener("load", function (e) {
    n(null, e)
  }, !1), o && i.addEventListener("error", function (e) {
    o(e)
  }, !1), r.parentNode.insertBefore(i, r)
}

const tools = {
  loadJSAsync,
}

export default tools;
//googleSDK.js - external dependency
import tools from './tools';

let defer;
function get() {
  if (defer === undefined) {
    defer = new Promise((resolve, reject) => {
      tools.loadJSAsync('maps.googleapis.com/maps/api/js?key=API_KEY',
        () => resolve(window.google),
        () => reject());
    });
  }
  return defer;
}

const googleSDK = {
  get,
};

export default googleSDK;

And usage (from mounted hook):

import googleSDK from './googleSDK';

googleSDK.get().then((google) => {
// Do your library stuff in here
});

The benefit of this is that you would lazy load your dependencies and not worry about builds.

The down side of it however is, that dependency management is not as nice as through npm and if your app is mainly build around specific dependency, does not make sense to lazy load it.

All 19 comments

Hi, shyamchandranmec!

If you want to use the library only on one page you can use mounted hook, it executed only in client side.

Also, you can check browser build like:

if (process.BROWSER_BUILD && window) {
  window.some_lib = require('some_lib')
}

@shyamchandranmec @KonstantinVlasov Also please see client side only plugins section. About smaller plugins (not ckeditor!) bundling in vendor.js may also help improving page load performance ;)

Hi @pi0 !
Wow, it is a nice improvement! will use it instead of ugly process.BROWSER_BUILD :)

@pi0 The client side only plugins doesn't work. It throws the error in the import statement.

@KonstantinVlasov I am not sure if the require() call is synchronous. I actually wanted to use a normal import statement which executes only on client side.

For now I included it with vendor.js But if I have many other libraries this will increase the bundle size.
@alexchopin Is there any other way to do it?

@shyamchandranmec Client-side plugins (ssr: false) just works, make sure you use the last version of nuxt (0.10.5 now), maybe you need to npm update.
require() works synchronous like simple import, one different: we can't use import from if statement, but we can use require()

If you have errors with client-side plugins please provide more info.

Here is my plugins/chart.js

import Chart from 'chart.js';
export default Chart;

nuxt.config.js

 plugins: [
    '~plugins/vuelidate',
    {src: '~plugins/chart', ssr: false}
],

component.vue

<script type="text/babel">
    import Chart from '~plugins/chart';
    export default () {
    methods: {
        drawChart () {
            //use Chart
        }
    }
    }
</script>

With this code i get this error

[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside.....

If I remove the import it works fine.

This is not an error. It is Vue warning about different DOM tree in server and client-side. It's common warning when you use client-side plugins because libraries like charts or maps change DOM tree.

All works fine with this warning.

@KonstantinVlasov oh. I thought this will affect the functionality of the page. But seeing that warning on the console every time is a bit annoying 😄

is any possible disabled some we already understand warning ? like use lib without SSR .

@KonstantinVlasov
When I do the plugins approach. The page throws an error. When I comment out the import Chart from '~plugins/chart.js'. It works perfectly.
Here is the error

[nuxt.js] Cannot load components TypeError: Cannot read property 'toLowerCase' of undefined
at emptyNodeAt (eval at 1 (vendor.bundle.8cbed14….js:7), <anonymous>:4570:42)
at VueComponent.patch [as

Looks like the error somewhere in your component when you use chart.js, not in the plugin.

@KonstantinVlasov
This error vanishes if I put chart.js in vendor.js . The chart also draws on the screen.
When I add the import it creates this problem

It's hard to understand problem without working example

@shyamchandranmec : Did you find a solution? I have the same issue (I am importing leaflet).

What I usually do, I lazy load the library I need, when I need it, only on client side. But for some main dependencies, I want to include them on initial load.

@SinisaG I just spent the past hour and a half dealing with the same issue. I'm now requireing leaflet when the component is mounted. Not ideal, but it'll work. Thanks.

Also experiencing the same toLowerCase error as @shyamchandranmec when using leaflet as a plugin.

I think I've setup a repository similar to how @shyamchandranmec was (but with Leaflet): https://github.com/xemoka/nuxt_leaflet_i469

I don't necessarily have a problem with requireing leaflet in mounted(), but it seems like the "[nuxt.js] Cannot load components TypeError: Cannot read property 'toLowerCase' of undefined" might be something "interesting" to investigate?

@SinisaG Here are my suggestions

  • plugins with ssr: false doesn't work as expected always.

  • Another way is to use process.BROWSER_BUILD { var lib = require('lib') }

  • Some libraries which are not compatible with webpack, I include it as a script tag in the page which needs it.

@xemoka : Thanks for setting up the example, it's exactly the same use case/setup I have as well.

@shyamchandranmec : Thanks, for suggestions.

At the end, I just require it inside of mounted hook. It disturbs my code style a bit, but it works. :) Thanks guys for suggestions and maintainers for this awesome nuxt project!

Extending on @shyamchandranmec suggestions, this is my setup usually for client side only libraries:

// tool.js - this is just a loader

/* eslint-disable */
function loadJSAsync(e, n, o) {
  const t = document,
    a = "script",
    i = t.createElement(a),
    r = t.getElementsByTagName(a)[0];
  i.src = "//" + e, n && i.addEventListener("load", function (e) {
    n(null, e)
  }, !1), o && i.addEventListener("error", function (e) {
    o(e)
  }, !1), r.parentNode.insertBefore(i, r)
}

const tools = {
  loadJSAsync,
}

export default tools;
//googleSDK.js - external dependency
import tools from './tools';

let defer;
function get() {
  if (defer === undefined) {
    defer = new Promise((resolve, reject) => {
      tools.loadJSAsync('maps.googleapis.com/maps/api/js?key=API_KEY',
        () => resolve(window.google),
        () => reject());
    });
  }
  return defer;
}

const googleSDK = {
  get,
};

export default googleSDK;

And usage (from mounted hook):

import googleSDK from './googleSDK';

googleSDK.get().then((google) => {
// Do your library stuff in here
});

The benefit of this is that you would lazy load your dependencies and not worry about builds.

The down side of it however is, that dependency management is not as nice as through npm and if your app is mainly build around specific dependency, does not make sense to lazy load it.

@SinisaG How do you get webpack to serve your script that you lazy load as a chunk not in app or common webpack files? Whatever, I put in vendor ends up in common webpack file. If the resource was not external like maps in your example?

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

maicong picture maicong  Â·  3Comments

mattdharmon picture mattdharmon  Â·  3Comments

surmon-china picture surmon-china  Â·  3Comments

bimohxh picture bimohxh  Â·  3Comments

gary149 picture gary149  Â·  3Comments