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 beforeCreate
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 created 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
.
kebab-case make-red makeRed vue-server-rendererAttention: Be aware to pass in your directives in 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 and
window
document
- Keep in mind that and
created
are executed on the server and client side. No side effects, nobeforeCreate
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!
Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.