Vue SSR and external dependencies

Ismayil Khayredinov
ITNEXT
Published in
4 min readJul 4, 2019

--

Source: Outcrawl

Update Jul 8, 2019: You can now see these principles in action in a VuePress documentation site.

If you dealt with VuePress or Nuxt, you know that window is the number one enemy of SSR. Debugging build errors is painful, because traces all lead to hydration logic, and it’s really hard to pinpoint the exact point of failure, especially if you are dealing with third-party components. After wasting hours trying to make these components work and trying to hack my way through to make them SSR-friendly, I set out to find a generic pattern for components that rely on third-party scripts and dependencies. I think I may have found a solution that so far works well in SSR environments. Let me illustrate the approach using two examples: a component that depends on a remote script (ReCaptcha) and a component that requires a browser-only module (CodeMirror).

The pattern can be generalized as follows:

  1. Create a Vue plugin that will extend the prototype with an async loader and register your component.
  2. Use the async loader in your component’s mounted() hook.

To freshen up your SSR knowledge, mounted() hook is only called in the browser (unlike created() hook, which is called twice), so it’s a safe(r) place to load external scripts and/or dependencies. The downside is that there may be a slight lag before the component is visible to the user, as such you may want to use a spinner, if your component is crucial to user experience. The upside however is that you no longer need to wrap your component in framework-specific tags, such asClientOnly or no-srr, or worry about random errors that popup in a production built after your component has worked perfectly well in development.

Component with a remote dependency

Let’s create a reCaptcha plugin:

For a reCaptcha to work, we need to load a script from Google’s servers. I personally don’t like loading “creepy” third-party scripts (I mean scripts from Google, Facebook and everyone else that come with a cookie baggage, potential trackers and full power of what a piece of JS can do on your page, which among other things can capture every mouse movement and every keyboard stroke, including passwords you type) on every pageload, so putting it into our .html template or extending header tags via config is not an option for me. As a side note, I must admit I find it puzzling, why the use of “innocuous” third-party tools, such as captchas, analytics et al is not prohibited under HIPAA and other privacy-concious policies. When Stripe and others tell me to put their script on every page load to allegedly prevent fraud, I say no-thank-you, I trust you not to do the right thing by my users. I personally can’t review every iteration of every third-party script I pull into my pages, so it’s only fair I treat every such script as a potential vulnerability.

In the above plugin, we extend the Vue prototype with a loader that returns a promise, which resolves to an instance of ready recaptcha, which will make it easier for us to chain our logic in the component. Should the recaptcha library be already present on the page, we simply resolve the promise.

Now, let’s create a recaptcha input:

As you can see, whenever we need to access an instance of recaptcha, we call this.$recaptcha.load() and then the promise.

Afterwards, we just need to register our plugin and can safely use vue-recaptcha module in our components, without having to worry about SSR.

Component with an npm dependency

Now, let’s do something similar with CodeMirror, which can be loaded from npm.

In this plugin, we are loading a number of scripts and stylesheets from codemirror node module. In case of node modules, we don’t need to worry about duplicate imports, as we did with external scripts.

A corresponding Vue component would look like follows:

Here we use the loader to import the codemirror library and any additional mode scripts we may need (by passing them as arguments to the loader). Then the component is rendered once all the dependencies have been resolved in the browser.

— — — — — — — — —

I believe this pattern is suitable for a variety of use cases that involve external dependencies that are not SSR-friendly. There may still be some caveats that I am not factoring in, and I will be sure to report back if I find them. In the meantime, I am looking forward to your feedback and/or criticism.

--

--

Full-stack developer, passionate about front-end frameworks, design systems and UX.