Vue Router 4: Route Params Not Available on Created/Setup


You may have noticed that the Vue 3 version of Vue router (4.0) came with a few breaking changes from its predecessor. Most of the changes are easy to spot, and the migration process is very straightforward. However, a very obscure but important change is often overlooked that can lead to hard-to-debug behavior: All navigations are now always asynchronous.

If you’ve been wondering why query params in your URL are nowhere to be found in your setup method or created hooks, but they still appear on your template when you interpolate them, stick around because we’re about to dive into what’s going on in those situations.


All navigations are now always asynchronous

To explore this, we’ll use a barebones Vue 3 skeleton app with Vue router 4.0 already installed. You can follow along with the code in this repo.

Go ahead and npm install if you haven’t done so already, and npm run serve the project locally. When you first visit the localhost URL, you will see that we are attempting to output the contents of our URL’s query params onto the page directly.

Vue logo

If you now go ahead and add some query params to the URL, like so: <a href="http://localhost:8080/?param=1" target="blank">http://localhost:8080/?param=1</a> and reload the page, the page will correctly reflect the newly added param.

Untitled

Let’s take a look inside App.vue, ****where I’ve also added a created hook into our component. You’ll see a console.log line that is outputting the contents $router.query like we are in the template. Let’s go ahead and run the experiment again with and without the query param like we did a minute ago.

You will notice that no matter how many params you add, or how many times you reload the page, the object that is being logged into the console is empty. Let’s unwrap this and then I will show you how we can very easily fix this behavior.

As I alluded to at the beginning, one of the often overlooked Vue Router 4 breaking changes is that all navigations are now always asynchronous. This becomes more obvious when working with transitions, as the documentation suggests, because it will trigger the animation as the Router goes from empty to populated by data.

The reason why we can see the param in the browser, but not in the console, is because Vue’s reactivity system is kicking in and updating the HTML with the Router’s query object as soon as it is available. Remember, it is now async. This happens so quickly that to us it seems like it was always there, which can be really confusing when working with query params in setup functions or lifecycle hooks like created().


Fixing the problem

Luckily the fix for this is very straightforward. We just need to go into main.js where we are mounting our application and wait until the Router is done loading and is ready to mount our app, so that all the Router content is immediately available to our code.

📃 main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)

// Replace -> app.mount('#app')
router.isReady().then(() => {
    app.mount('#app')
})

If you go back to the browser and add the params and reload, you will now correctly see in the console window that the params are now available to us in the mounted hook. You can also try this with a setup function if you prefer the composition API.


Wrapping up

I hope this article helped you get unstuck (and understand the why) on a hard-to-debug but easy-to-fix Vue Router problem. See you in the next one!

Download the cheatsheets

Our new Vue 3 Migration guide, Composition API, Vue.js essentials, Pinia, and Nuxt.js cheat sheets save you time and energy by giving you essential syntax at your fingertips.