menu

Client-Only works as expected in Nuxt 3. Script code is not evaluated server-side! The post below is for Nuxt 2.

Common Problems With The Nuxt Client-Only Component

When I first started learning Nuxt, I thought the <client-only> component was the jack of all trades. It's obvious right? If there is something you want to run in the browser, just wrap it in a <client-only> component and then move on. It turns out, it isn't that simple.

TLDR: The <client-only> component doesn't do what you think it does. Yes, it skips rendering your component on the server side, but it still gets executed!

Here is a simple example. Let's say the script below is a simple page using some kind of 3rd party library that requires window or document.

index.vue
<template>
  <div>
    <client-only>
      <jot-form/>
    </client-only>
  </div>
</template>

<script>
import JotForm from '@/components/jotForm'
export default {
  components: {
    JotForm
  }
}
</script>
jotForm.vue
<template>
<iframe
    id="JotFormIFrame-12345"
    title="Cats"
    onload="window.parent.scrollTo(0,0)"
    allowtransparency="true"
    allowfullscreen="true"
    allow="geolocation; microphone; camera"
    src="https://form.jotform.com/12345"
    frameborder="0"
    style="
      min-width: 100%;
      height:539px;
      border:none;"
    scrolling="no"
  ></iframe>
</template>

<script>
 // client-only protects us right... (cough 'not really')
 var ifr = document.getElementById("JotFormIFrame-12345");
    if (window.location.href && window.location.href.indexOf("?") > -1) {
      var get = window.location.href.substr(
        window.location.href.indexOf("?") + 1
      );
      if (ifr && get.length > 0) {
        var src = ifr.src;
        src = src.indexOf("?") > -1 ? src + "&" + get : src + "?" + get;
        ifr.src = src;
      }
    }
</script>

Even though jotForm.vue is nested within a <client-only> component, IT WILL STILL EXPLODE SERVER SIDE!

Solutions:

  • lazy load your client component const form = () => import('...')
  • move your client code into the mounted() hook
  • wrap your client code inside an if(process.client) statement
Updated
<script>
export default {
  // By moving the code that needs window & document to mounted, now it will
  // only be executed client-side
  mounted() {
    var ifr = document.getElementById("JotFormIFrame-12345");
    if (window.location.href && window.location.href.indexOf("?") > -1) {
      var get = window.location.href.substr(
        window.location.href.indexOf("?") + 1
      );
      if (ifr && get.length > 0) {
        var src = ifr.src;
        src = src.indexOf("?") > -1 ? src + "&" + get : src + "?" + get;
        ifr.src = src;
      }
    }
  }
}
</script>

Problem Libraries

Sometimes when you use vue libraries with SSR (Nuxt) you get window or document errors just by importing them! This means they aren't SSR compatible and you should move their import into a client-side plugin.

Summary

At this point, you may be thinking; "Josh, if I have to use other methods to protect the server from window or document, why would I need <client-only> at all?"

Super good question, I'm glad you asked.

I would say for a vast majority of people, they wouldn't want to use it at all. Me, I use it along with one of the 3 methods above just to keep all of my third party browser-side components totally client-side. But hey, I'm a little weird.

That's all for now. Feel free to drop me a line on Twitter and tell me what you think!