Vue Route Component Hooks

How to request data in a universal web application

One of the most challenging parts of building a universal web application with Vue is figuring out which Vue and Vue Router lifecycle hooks to use for requesting data during a route’s lifecycle.

To tackle this problem you should have a good understanding of the route lifecycle and when all of the Vue and Vue Router lifecycle hooks are called.

In this article, we’ll cover these topics and how to create custom route component hooks to request data in a way that make sense for your application.

Things To Know Before Reading:

The Route Lifecycle

The route lifecycle begins with a navigation request to the router. The router finds a matching route and resolves the asynchronous route components. If navigation is not rejected, navigation to the matched route is confirmed and the route components are rendered. Cool. So where in the route lifecycle should we request data?

Categorize Your Data

In my experience data for any given route can be put into three categories: Permissions, Critical, and Lazy.

Permissions — Data required to determine whether a user has access to a route. This data should be requested and available before navigation is confirmed in order to determine whether they can enter the route.

Critical — The most important data for a route’s user experience. This data should be requested as soon as navigation has been confirmed. Whether or not this data needs to be available before rendering is up to you and the experience you want your users to have.

Lazy — Supplementary data that is not critical to the user experience. This data should be requested after the critical data and should not block rendering.

This is a basic model for when data should be requested during a route’s lifecycle.

Actually…The Four Route Lifecycles

As it turns out, in a Vue universal web application any given route can have four different routing lifecycles. Here is a quick summary of the lifecycles and their key differences:

Server Request

In the server entry point of an application a request is handled and goes through a route lifecycle to return the initial route data and html. During this lifecycle, critical data must be available before rendering and lazy data should not be requested because we don’t want it to hold up rendering.

Client Initialization

In the client entry point of an application another route lifecycle takes place in order to initialize the application. Permissions and critical data don’t have to be requested because they have already been requested by the server. Lazy data can be requested for the initial route while the application initializes.

Client Navigation

After client initialization, the user navigates to a new route that uses different components than the current route. In this case, we go through a full route lifecycle on the client requesting permissions, critical data and lazy data. Unlike the Server Request lifecycle it’s up to you whether critical data must be be available before rendering the route components.

Client Update Navigation

Same as the Client Navigation lifecycle except that a user navigates to a route that uses the same components as the current route (e.g. /profile/123/ -> /profile/456/). In this case, a different set of update hooks get called because the route has already been entered and the components have already been created.

Let’s update our model.

Lifecycle Hooks

Now that we have a model of when data should be requested, let’s see which lifecycle hooks are best for requesting data at those points. The diagram below shows the Vue and Vue Router lifecycle hooks that get called by each of the route lifecycles.

A few notes about this diagram:

Navigation confirmation is implied within the onReady hook if the matched route is not rejected.

hook if the matched route is not rejected. beforeEach , beforeResolve , and afterEach are, in general, intended for navigation on the client.

, , and are, in general, intended for navigation on the client. The beforeRouteLeave, beforeDestroy and destroyed lifecycle hooks that get called during the Client Navigation lifecycle are intentionally left out. These hooks are called at the end of a route’s life making them not a great choice for requesting data.

Which Hooks Should I Use?

With a full picture of when lifecycle hooks are called we can start to see why some lifecycle hooks are better for requesting data than others.

Hooks That Happen After Components Have Been Resolved

A route’s request logic should live within the route component so that it can be code split. Any lifecycle hooks that happen before a route component has been resolved are not a great option because you will end up bloating your initial script with route specific code.

DISQUALIFIED: beforeEach, beforeEnter

Hooks That Are Not Redundant Between Server Request and Client Initialization

Any lifecycle hooks that get called during the Server Request lifecycle and the Client Initialization lifecycle can be disqualified. Anytime we request data in these hooks we end up either requesting it again or having to add a condition to check whether the code is running server or client side in order to avoid duplicate requests.

DISQUALIFIED: beforeEnter, beforeRouteEnter, beforeCreate, created

Hooks That Are Consistent Between Client Navigation and Client Update Navigation

Next we can disqualify any lifecycle hooks that are inconsistent between Client Navigation and Client Update Navigation lifecycles. If you put your request logic in these hooks your data will either not get requested in one of the lifecycles or you will have to repeat your request logic across multiple lifecycle hooks.

DISQUALIFIED: beforeEnter, beforeRouteEnter, beforeRouteUpdate, beforeCreate, created, watch $route, beforeMount, mounted, beforeUpdate, updated

What are we left with?

Will the onReady, beforeResolve, and afterEach hooks enable us to request data as described in our model? With the help of some custom route component hooks, absolutely.

Route Component Hooks

Route component hooks are custom functions that are optionally defined in a route component that can be used during route lifecycles.

Tying It All Together

We have defined where in the route lifecycles we want to fetch data and we know what lifecycle hooks to use. However, we can’t define every single route’s request logic in the global lifecycle hooks. Instead, we should define each route’s logic within it’s own custom route component hooks and have the lifecycle hooks invoke them.

In our Server Request lifecycle we can use the onReady lifecycle hook to invoke the permissions and criticalData route component hooks.

In our Client Initialization lifecycle we can use the onReady lifecycle hook to invoke the lazyData route component hook. We can also subscribe to the beforeResolve and afterEach lifecycle hooks that will invoke the permissions, criticalData, and lazyData route component hooks during subsequent navigation.

Let’s see everything tied together in a code example.

The Code

The following coding example shows how simple it is to setup custom route component hooks via the server and client entry points of a universal web application.

Server Entry Point

Client Entry Point

Example Route Component

Benefits Of Route Component Hooks

Code Splitting — All route specific code for requesting data can live within the route components so that it is easily code split.

Readability — By looking at a route component one can easily identify the places where data is requested for a route.

Maintainable — Code for requesting data is added in a predictable set of functions when designing route components and will work in all of the route lifecycles.

Performance — Navigation is only blocked when it needs to be and requests are made in parallel across a route’s matched components whenever possible.

Closing thoughts

While this article contains opinionated models and code examples, the overall ideas are flexible and I would encourage you to adapt them for your own purposes.

I hope you found this article helpful and I’m eager to hear the Vue community’s thoughts.