RSSAmplifier

Alexander Lichter | Blog · Oct 20, 2018

The Guide To Write Universal SSR-ready Vue Components

0
Sign in to vote or save

Alexander Lichter · www.lichter.io

As a Vue developer, you may have heard the term server-side rendering (SSR). Even if you are not using a framework like Nuxt.js or an SSR-plugin, it is important to know how to write universal components, or to put it in simpler words, components that can be interpreted from the server and the client.

If you ever want to switch to an SSR-based approach or share your component with people who do, this knowledge will definitely make your life easier! As a library/plugin author, this is a must in my opinion. And guess what, it isn’t even hard!

Common pitfalls

There are three very common and very problematic caveats that developers should think about when writing universal components. We’ll go through all of them one by one, showing examples of wrong and right implementations!

We will use this CodeSandBox for the examples.

window, document, and friends - platform-specific APIs

When the component is processed on the server-side, no dynamic updates will occur. That’s why only two lifecycle hooks will be executed on the server:

beforeCreate

and

created

. This means also, that those two hooks will be called twice. Once on the server and once on the client side.`

But on the server-side, there is no

window

and no

document

available (and no other browser-specific API like

fetch

. If you try to call them in these two hooks, an error will be thrown on the server. The component cannot be server-side-rendered anymore! That behavior can also be observed in our

example for wrong usage

. SSR-users could still use the component wrapped in a

<ClientOnly></ClientOnly>

tag but then it won’t have any impact on SEO and take longer to be visible at all.

This is by far the most common problem with “normal” components or 3rd party libraries in a server-side-rendered environment.

Rule of thumb: Don’t call browser-specific APIs in

created

or

beforeCreate

. If you have to do this, then at least perform an availability check:

export default {
  created() {
    if (typeof window !== "undefined") {
      window.scroll(/*...*/);
    }
  },
};

But in most situations, it is completely fine to call them in

beforeMount

or

mounted

. If you have to use an API on the server and the client, make sure to have to available on both sides (for example use

isomorphic-fetch

or

axios

) if you want to send AJAX requests.

Also, you can sometimes leverage

this.$el

inside a component (where

$el

is the DOM element of the component itself). This can come in handy when binding event listeners or doing query selections.

An example component of correct usage can be found under

components/platform/right.vue

in the CodeSandbox. The “bad example” is the

wrong.vue

component in the same folder.

Lifecycle hooks and side effects

Speaking of lifecycle hooks! There is another thing you should think about:

side effects

. A function or expression has a

side effect

when it touches or modifies some state outside of the local environment. Examples are API calls, I/O operations, setting timers, using randomness, adding listeners, and so on.

To avoid memory leaks, you do not want to have side-effects in your

created

and

beforeCreate

hooks and you already know why! As these hooks getting called from the server-side as well, you cannot close a connection there or clear an interval. Instead, these objects will stay around forever and add up, causing a memory leak!

Rule of thumb: Don’t use code with

side-effects

in

created

or

beforeCreate

.

The examples can be found under

components/side-effects.vue

in the CodeSandbox. Every time the

Wrong.vue

component will be rendered on the server-side, a new interval will stay in the memory forever.

But when will it be server-side rendered? The answer is: every time a user enters the app over the

side-effects/wrong.vue

page (under

pages

).

Because the first page-request to the app will be server-side-rendered and all subsequent requests (through page navigation, on-page redirects and so on) will be rendered by the client. Page refreshes and other external redirects to the app count as “entry points” and will be server-side rendered.

No data reactivity

This is usually no big problem but it’s not bad to mention it anyway. There is no reactivity between the values on the server-side and on the client side.

If you manipulate your

data

on the server-side, these changes will not be taken into account on the client side at all. If you jump into the

bad example

, you’ll see that the value quickly changes from

10

to

0

fast. In the code, I set zero as the default value in the data function and 10 on the server-side created method.

Directives

Custom Vue directives are often used to manipulate the DOM (eg. revealing elements on scroll or make them stick to a specific position). This won’t work on the server-side as we now know. But what can be done there?

Well, the easiest way is: Don’t use directives as the abstraction, use components. I did this with components like VueNextLevelScroll or vue-if-bot because it is way easier to make them universally usable and they can be code-split as well! You don’t lose anything by choosing components as an abstraction.

If you really want to use directives, you can add a server-side equivalent of this directive. In Nuxt, this is possible by setting up a

directives

object in the

this.options.render.bundleRenderer

object in the

nuxt.config.js

. A good (but complex) example is the official

v-model

ssr directive

.

Attention: Be aware to pass in your directives in

kebab-case

(

make-red

instead of

makeRed

). Otherwise, they won’t be recognized! This is a bug in

vue-server-renderer

(follow this issue for more info). This has been fixed in Vue 2.5.19!

The matching example will show you why you definitely should use components or server-side directives. Do you notice the flickering for the client-side-only directive example? It will be noticeable for all users that use this page as an entry point.

Conclusion

Alright, let’s wrap it up, folks! If you want to go through the example code line by line, please take a look into the CodeSandBox.

  • Be careful when using platform-specific APIs, especially

    window

    and

    document

  • Keep in mind that

    created

    and

    beforeCreate

    are executed on the server and client side. No side effects, no

    window

  • There is no reactivity on the server side
  • Using directives isn’t always the best abstraction. But if you do use them, provide a server-side directive

If you want further reading, I suggest reading the official vue-ssr-docs!

Still have questions? No problem, tweet me at @TheAlexLichter, reach out on the Vue/Nuxt Discord or write me a mail (blog at lichter dot io)

I hope you enjoyed that article and are now a bit more aware of server-side-rendering compatibilities! If this is the case, I’d gladly ask you to spread the word!

Read the original on lichter.io

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.