This is a blog post by Morgan Brown

Vue, Server Side Rendering, and Handling Dependencies that Require a Browser Environment

In a recent project for a certain startup, I was tasked with creating graphs in an existing server-side rendered (SSR) Nuxt.js-based app. I elected to use Apexcharts because, well, it’s pretty cool, supports a lot of use cases, and has a convenient Vue component.

The problem with Apexcharts in an SSR context is that it requires a browser environment on import. This prevents the app from building, which is clearly a no go. I experimented with jsdom to get around this, but encountered some issues with a missing SVG API. I didn’t explore this route any further – having the graph rendered on the server isn’t that important. It just needs to work in the frontend client!

How can we tackle this? We need to avoid importing Apexcharts until we’re in a cosy browser environment. Luckily, Vue has support for asynchronous components, components that are only loaded and imported when they’re used. We’ll use this to tell Vue to import 'vue-apexcharts' only when the component <apexchart /> is rendered.

// main.js
Vue.component('apexchart', () => {
  // NB: tell webpack to include the dynamic import within
  // the main bundle instead of splitting into other bundles.
  // https://webpack.js.org/api/module-methods/#import-
  return import(
    /* webpackMode: "eager" */
    'vue-apexcharts'
  )
})
<!-- my-graph.vue -->
<template>
  <apexchart .../>
</template>

This is great, but our app still tries to render the component on the server. How can we delay the rendering until we’re in a browser? Here’s an excerpt from Vue’s SSR guide:

Since there are no dynamic updates, of all the lifecycle hooks, only beforeCreate and created will be called during SSR. This means any code inside other lifecycle hooks such as beforeMount or mounted will only be executed on the client.

We can delay the rendering of the component until it’s mounted (and thus “on the client”) with a simple v-if:

<template>
  <apexchart v-if="mounted" .../>
</template>
<script type="application/javascript">
export default {
  data () {
    return {
      mounted: false
    }
  },
  mounted () {
    this.mounted = true
  }
}
</script>

Now, Apexcharts will only be imported in a browser environment, where it has all the APIs it needs.

See something you want to change? Submit a proposal via Github.