31

I am trying to use Choices.js within a Vue component. The component compiles successfully, but then an error is triggered:

[vue-router] Failed to resolve async component default: ReferenceError: document is not defined

In the browser I see:

ReferenceError document is not defined

I think this has something to do with the SSR in Nuxt.js? I only need Choices.js to run on the client, because it's a client only aspect I guess.

nuxt.config.js

build: {
  vendor: ['choices.js']
}

AppCountrySelect.vue

<script>
import Choices from 'choices.js'

export default {
  name: 'CountrySelect',
  created () {
    console.log(this.$refs, Choices)
    const choices = new Choices(this.$refs.select)
    console.log(choices)
  }
}
</script>

In classic Vue, this would work fine, so I'm very much still getting to grips with how I can get Nuxt.js to work this way.

Any ideas at all where I'm going wrong?

Thanks.

Michael Giovanni Pumo
  • 14,338
  • 18
  • 91
  • 140

11 Answers11

45

It's a common error when you start a Nuxt project ;-)

The Choices.js lib is available only for client-side! So Nuxt tried to renderer from server-side, but from Node.js window.document doesn't exist, then you have an error.
nb: window.document is only available from the browser renderer.

Since Nuxt 1.0.0 RC7, you can use <no-ssr> element to allow your component only for client-side.

<template>
  <div>
    <no-ssr placeholder="loading...">
      <your-component>
    </no-ssr>
  </div>
</template>

take a look at the official example here: https://github.com/nuxt/nuxt.js/blob/dev/examples/no-ssr/pages/index.vue


Update:

Since Nuxt >= 2.9.0, you have to use the <client-only> element instead of <no-ssr>:

<template>
  <div>
    <client-only placeholder="loading...">
      <your-component>
    </client-only>
  </div>
</template>

To know more, see nuxt docs: https://nuxtjs.org/docs/2.x/features/nuxt-components#the-client-only-component

Nicolas Pennec
  • 7,533
  • 29
  • 43
  • Thanks for this! Any idea how I could then output the element itself with SSR? As in, some JS needs to act on the client-side for a select list, but I'd still like to render the actual select list on the server. – Michael Giovanni Pumo Sep 08 '17 at 13:31
  • 4
    How should the `` file look like? I have YourComponent.vue: ` ` and it still ends with `document is not defined` – Michal Skop May 21 '18 at 22:06
  • 2
    https://nuxtjs.org/guide/plugins#client-side-only `plugins: [ { src: '~/plugins/vue-notifications', mode: 'client' } ]` I would suggest this for those that are still struggling like I was to find a solution. – Jeff Bluemel Dec 18 '19 at 17:59
  • 1
    @JeffBluemel Your solution saved my day. I had to add this in my nuxt.config.js `{ src: '~/plugins/vue-izitoast', mode: 'client' }` – ManuEl Magak Apr 17 '20 at 17:53
  • 2
    @JeffBluemel, Yours is the only solution worked for me after banging my head for half a day for a solution. I literally tried everything I could find on the internet. – Imtiaz Aug 08 '20 at 20:23
  • If you are using a Nuxt version of > **v2.9.0**, use `` instead of ``. As `` is deprecated from Nuxt **v2.9.0** – Kazmi Aug 18 '20 at 06:50
31

The accepted answer (while correct) was too short for me to understand it and use it correctly, so I wrote a more detailed version. I was looking for a way to use plotly.js + nuxt.js, but it should be the same as the OP's problem of Choice.js + nuxt.js.

MyComponent.vue

<template>
  <div>
    <client-only>
      <my-chart></my-chart>
    </client-only>
  </div>
</template>
<script>
export default {
  components: {
    // this different (webpack) import did the trick together with <no-ssr>:
    'my-chart': () => import('@/components/MyChart.vue')
  }
}
</script>

MyChart.vue

<template>
  <div>
  </div>
</template>
<script>
import Plotly from 'plotly.js/dist/plotly'
export default {
  mounted () {
    // exists only on client:
    console.log(Plotly)
  },
  components: {
    Plotly
  }
}
</script>

Update: There is <client-only> tag instead of <<no-ssr> in Nuxt v>2.9.0, see @Kaz's comment.

Michal Skop
  • 1,349
  • 1
  • 15
  • 23
  • This worked in my case when I was using https://evodiaaut.github.io/vue-marquee-text-component. Mostly no-ssr tag is enough but with this library, I had to import the way as mentioned above. @michal. Can you explain the reason behind this kind of import – Baldeep Singh Kwatra Nov 12 '19 at 11:13
  • @BaldeepSinghKwatra unfortunately, I came to this solution by trial/error, as far as I remember – Michal Skop Nov 13 '19 at 15:36
  • 1
    If you are using a Nuxt version of > **v2.9.0**, use `` instead of ``. As `` is deprecated from Nuxt **v2.9.0** – Kazmi Aug 18 '20 at 06:52
14

You need to add it as a plugin and then disable SSR for it.

As the document and window are not defined on the server-side.

Your nuxt.config.js should look like below

plugins: [
  { src: '~/plugins/choices.js' } // both sides
  { src: '~/plugins/client-only.js', mode: 'client' }, // only on client side
  { src: '~/plugins/server-only.js', mode: 'server' } // only on server side
],
fredrivett
  • 5,419
  • 3
  • 35
  • 48
Yusuf Adeyemo
  • 389
  • 3
  • 12
  • 1
    now the syntax looks like this { src: '~/plugins/client-only.js', mode: 'client' }, // only on client side { src: '~/plugins/server-only.js', mode: 'server' } // only on server side – Arif Ikhsanudin Sep 15 '20 at 00:10
  • Thanks for the pointer @ArifIkhsanudin I just update the Answer. – Yusuf Adeyemo Sep 16 '20 at 10:53
7

I found that now the no-ssr is replace by , i am using echart and have the same problem but now it´s working!

    <client-only>
        <chart-component></chart-component>
    </client-only>
Mariel Quezada
  • 101
  • 2
  • 3
6

This thread is a bit old, but I will leave my solution here so maybe someone finds it useful.

I had similar issue with vue-star-rating and few other plugins recently.


Below steps can be followed and adjusted depending on the plugin name, import / usage settings:

  1. Go to your plugins folder and create new js file, in this case vue-star-rating.js, then edit it to setup the plugin:

import Vue from 'vue'
import VueStarRating from 'vue-star-rating'

Vue.component('vue-star-rating', VueStarRating); //<--- the name you used to register the plugin will be the same to use when in the component (vue-star-rating)
  1. Go to your nuxt.config.js file and add plugin:

  plugins: [{
      src: '~/plugins/vue-star-rating', // <--- file name
      mode: 'client'
    },
    //you can simply keep adding plugins like this:
    {
      src: '~/plugins/vue-slider-component',
      mode: 'client'
    }]
  1. Now you are ready to use the plugin anywhere in the application. However, to do that you will need to wrap it in the container <client-only>. Example:

<client-only placeholder="loading...">
   <vue-star-rating />
</client-only>

Notes:

You do not need to import anything locally to the component, simply using it like above should fix the problem.

Please make sure you are naming the plugin the same way in both places, step 1 and step 3. In this case it would be vue-star-rating.

Jakub A Suplicki
  • 4,586
  • 1
  • 23
  • 31
6

I had this error with lightgallery.js adding mode: 'client' seems helped

nuxt.config.js

 plugins: [
    { src: '~/plugins/lightgallery.js',  mode: 'client' }
  ],

plugins/lightgallery.js

import Vue from 'vue'
import lightGallery from 'lightgallery.js/dist/js/lightgallery.min.js'
import 'lightgallery.js/dist/css/lightgallery.min.css'

Vue.use(lightGallery)

ImageGallery.vue

<template>
  <section class="image-gallery-container">
    <div class="image-gallery-row">
      <div
        ref="lightgallery"
        class="image-gallery"
      >
        <a
          v-for="image in group.images"
          :key="image.mediaItemUrl"
          :href="image.mediaItemUrl"
          class="image-gallery__link"
        >
          <img
            :src="image.sourceUrl"
            :alt="image.altText"
            class="image-gallery__image"
          >
        </a>
      </div>
    </div>
  </section>
</template>

<script>
export default {
  name: 'ImageGallery',
  props: {
    group: {
      type: Object,
      required: true
    }
  },
  mounted() {
    let vm = this;

    if (this.group && vm.$refs.lightgallery !== 'undefined') {
      window.lightGallery(this.$refs.lightgallery, {
        cssEasing: 'cubic-bezier(0.680, -0.550, 0.265, 1.550)'
      });
    }
  }
}
</script>
atazmin
  • 4,757
  • 1
  • 32
  • 23
5
<script>
import Choices from 'choices.js'

export default {
  name: 'CountrySelect',
  created () {
    if(process.client) {
      console.log(this.$refs, Choices)
      const choices = new Choices(this.$refs.select)
      console.log(choices)
    }
  }
}
</script>

I guess this should help, nuxt will touch insides of computed after it renders on server and window will be defined

Puwka
  • 640
  • 5
  • 13
0

if you still want to do it, document object can be taken this way:

const d = typeof document === 'undefined' ? null : document
Zenderg
  • 11
  • 1
  • 2
0

For completeness, it's worth mentioning that instead of the object syntax in Yusuf Adeyemo answer (which I prefer as it separates out the file from how it is used), you can also set plugins to operate in client or server side only by naming the files like so:

export default {
  plugins: [
    '~/plugins/foo.client.js', // only in client side
    '~/plugins/bar.server.js', // only in server side
    '~/plugins/baz.js' // both client & server
  ]
}

src: https://nuxtjs.org/docs/directory-structure/plugins/#client-or-server-side-only

fredrivett
  • 5,419
  • 3
  • 35
  • 48
0

On top of all the answers here, you can also face some other packages that are not compatible with SSR out of the box (like in your case) and that will require some hacks to work properly. Here is my answer in details.

The TLDR is that you'll sometimes need to:

  • use process.client
  • use the <client-only> tag (be careful, it will not render but still execute the code inside)
  • use a dynamic import if needed later on, like const Ace = await import('ace-builds/src-noconflict/ace')
  • load a component conditionally components: { [process.client && 'VueEditor']: () => import('vue2-editor') }

With all of this, you're pretty much covered for every possible case.

kissu
  • 40,416
  • 14
  • 65
  • 133
0

I was trying to access document in created hook so when I moved the logic from created hook to mounted hook, my problem was solved.

hayatbiralem
  • 649
  • 8
  • 15