Use more client-side libs with Nuxt SSR

May 14th 2021 NuxtJS

Server-side rendering is a great way to make your NuxtJS site more SEO-friendly, as it sends a fully rendered page to the client. However, this doesn't work with libraries that require access to DOM objects like document and window. These can only be used on the client. To ensure that such code is not executed on the server, you can place it in the beforeMount or mounted hooks, which are only triggered on the client. Unfortunately, this is not sufficient for some libraries.

One such example is the JavaScript checkout library for the Payvision payment gateway. If you follow the advice above, placing the sample code from the official library documentation into the mounted hook of a NuxtJS page should suffice:

<template>
  <div>
    <div>Header</div>
    <div id="checkout-container"></div>
  </div>
</template>
import Vue from "vue";
import Checkout from "@payvision/checkout-library";

export default Vue.extend({
  mounted: () => {
    const options = {
      live: false,
    };

    const checkout = new Checkout(
      "{{CHECKOUT_ID}}",
      "checkout-container",
      options
    );
    checkout.render();
  },
});

This code works fine when server-side rendering is disabled. However, as soon as you enable it, it fails with the following error:

ReferenceError: window is not defined

Of course, the window object is not defined on the server. But why is the code executed on the server if it is placed in the mounted hook?

The code in the mounted hook is not executed at all.You can try commenting out everything in it, and the page will still fail with the same error. The offending code accesses the window object when the Checkout object is imported:

import Checkout from "@payvision/checkout-library";

To fix the error, don't import this object on the server in the first place. How? By importing it dynamically at the top of the mounted hook instead:

import Vue from "vue";

export default Vue.extend({
  mounted: () => {
    const Checkout = require("@payvision/checkout-library");

    const options = {
      live: false,
    };

    const checkout = new Checkout(
      "{{CHECKOUT_ID}}",
      "checkout-container",
      options
    );
    checkout.render();
  },
});

With this change, the code works even if server-side rendering is enabled, since the Checkout object is no longer imported on the server.

You can get a NuxtJS sample project with the code from this post in my GitHub repository. The last commit contains the final working code. The one before that contains the code that only works with server-side rendering disabled and fails on the server.

If you have NextJS server-side rendering enabled, keep in mind that not all JavaScript code will work on the server because there is no DOM available there. Usually it helps to put the offending code in the beforeMount or mounted hook. But some libraries run their client-side code as soon as you import them into a file. In these cases, it helps to import them in a client-side hook using the require function.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License