Skip to content

Commit 46822e3

Browse files
Merge pull request #26 from skirtles-code/accessing-the-route
Update accessing-the-route.md
2 parents ab48b2c + c0a702f commit 46822e3

File tree

3 files changed

+150
-9
lines changed

3 files changed

+150
-9
lines changed

docs/.vitepress/config.mts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,10 @@ export default defineConfig({
122122
text: 'How do I create unique element ids with Vue?',
123123
link: '/faq/unique-element-ids'
124124
},
125-
// {
126-
// text: `Why can't I use the current route inside <code>App.vue</code>?`,
127-
// link: '/faq/accessing-the-route'
128-
// },
125+
{
126+
text: `Why can't I use the current route in <code>App.vue</code>?`,
127+
link: '/faq/accessing-the-route'
128+
},
129129
// {
130130
// text: `Why does my logging show an empty/missing value after I've loaded the data?`,
131131
// link: '/faq/logging-after-loading'

docs/faq/accessing-the-route.md

Lines changed: 145 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,148 @@
1-
# Why can't I use the current route inside `App.vue`?
1+
# Why can't I use the current route in `App.vue`?
2+
3+
The short answer is *timing*. You can use the current route in `App.vue`, but the initial render will usually occur before the route is resolved.
4+
5+
## Understanding the problem
6+
7+
Let's imagine we have an application structured like this:
8+
9+
```
10+
App
11+
+- RouterView
12+
+- HomePage
13+
```
14+
15+
Here, `HomePage` is the route component for the current route, shown by the `RouterView`.
16+
17+
Inside `HomePage` we might want to access the current route, maybe so we can access `params` or `query`. That's no problem, we can just use `useRoute()`:
18+
19+
::: code-group
20+
21+
```vue [Composition API]
22+
<!-- HomePage.vue -->
23+
<script setup>
24+
import { useRoute } from 'vue-router'
25+
26+
const route = useRoute()
27+
28+
console.log(route.params.id)
29+
</script>
30+
```
31+
32+
```vue [Options API]
33+
<!-- HomePage.vue -->
34+
<script>
35+
export default {
36+
created() {
37+
console.log(this.$route.params.id)
38+
}
39+
}
40+
</script>
41+
```
242

3-
::: warning This answer is a stub.
4-
We are still working on writing the answers to the FAQ questions. The answer below is incomplete, but you may still find it useful.
543
:::
644

7-
The short answer is *timing*. You can use the current route inside `App.vue`, but it won't be available in time for the initial render.
45+
If you're using the Options API then you'd use `this.$route` instead, but it works out much the same.
46+
47+
If `HomePage` has child components then we can also use `useRoute()` or `this.$route` to access the current route in those components.
48+
49+
But it gets a bit trickier if we're outside the route component, e.g. in `App.vue`.
50+
51+
Resolving the route is not a synchronous process. [Navigation guards](https://router.vuejs.org/guide/advanced/navigation-guards.html) and [lazy-loaded components](https://router.vuejs.org/guide/advanced/lazy-loading.html) can be asynchronous, so the route won't be resolved until they are complete. Even if you aren't using those features, Vue Router uses promises internally, so the route still won't be resolved synchronously.
52+
53+
When you first mount the application, `App` will render, but the route won't be resolved yet, so the `RouterView` will be empty.
54+
55+
It is still possible to access `useRoute()` or `this.$route` during that initial render, but the route object will just be a placeholder. It won't yet contain the resolved values for the route.
56+
57+
## A realistic example
58+
59+
Consider the following example. It tries to use the current route in `App.vue` to decide which layout to wrap around the `RouterView`:
60+
61+
```vue
62+
<!-- App.vue -->
63+
<script setup>
64+
import DefaultLayout from './layouts/DefaultLayout.vue'
65+
</script>
66+
67+
<template>
68+
<component :is="$route.meta.layout || DefaultLayout">
69+
<RouterView />
70+
</component>
71+
</template>
72+
```
73+
74+
During the initial render, the `$route.meta` won't be populated yet, so `layout` will just be `undefined`. It'll use the `DefaultLayout` for that initial render, no matter which route was accessed. This can lead to the page flashing, as it renders `DefaultLayout` and then jumps to an alternative layout once the route is resolved.
75+
76+
There are a few ways we might fix this problem.
77+
78+
## Deferring mounting
79+
80+
Vue Router provides the method [`isReady()`](https://router.vuejs.org/api/interfaces/Router.html#isReady), which can be used to wait until it has resolved the route. We could use it to defer mounting the application until the route is ready:
81+
82+
```js
83+
// main.js or main.ts
84+
// ...
85+
86+
const app = createApp(App)
87+
88+
app.use(router)
89+
90+
router.isReady().then(() => {
91+
app.mount('#app')
92+
})
93+
```
94+
95+
This approach is often used in SSR (server-side rendering), where both the server and client need to render the route component as part of the initial render. But it can also be used in applications without SSR.
96+
97+
The potential problem with this approach is that it delays all rendering until the router is ready. If you're using SSR then this isn't likely to be a problem, as the user will see the server-generated content straight away and won't notice the small delay in hydration. But if you aren't using SSR, the Vue portion of your page will just be missing until it resolves. Most likely, your users will just see an empty page.
98+
99+
Depending on your application, that might not matter. A brief pause before the page shows might be good enough, especially if the alternative is a page that flashes between layouts.
100+
101+
## Extra handling in `App.vue`
102+
103+
With a bit of extra effort, we could show a loading indicator if the route isn't resolved yet. There are a few ways we might implement this. One approach would be to use `router.isReady()` inside `App.vue` to track when the router is ready. e.g.:
104+
105+
```vue
106+
<!-- App.vue -->
107+
<script setup>
108+
import { shallowRef } from 'vue'
109+
import { useRouter } from 'vue-router'
110+
import DefaultLayout from './layouts/DefaultLayout.vue'
111+
import LoadingIndicator from './components/LoadingIndicator.vue'
112+
113+
const router = useRouter()
114+
115+
const fallback = shallowRef(LoadingIndicator)
116+
117+
router.isReady().then(() => fallback.value = DefaultLayout)
118+
</script>
119+
120+
<template>
121+
<component :is="$route.meta.layout || fallback">
122+
<RouterView />
123+
</component>
124+
</template>
125+
```
126+
127+
Here we're using the `LoadingIndicator` component in place of the layout. The `LoadingIndicator` will ignore the slot content.
128+
129+
We could also implement this using the slot of `RouterView`:
130+
131+
```vue
132+
<!-- App.vue -->
133+
<script setup>
134+
import DefaultLayout from './layouts/DefaultLayout.vue'
135+
import LoadingIndicator from './components/LoadingIndicator.vue'
136+
</script>
137+
138+
<template>
139+
<RouterView v-slot="{ Component }">
140+
<component v-if="Component" :is="$route.meta.layout || DefaultLayout">
141+
<component :is="Component" />
142+
</component>
143+
<LoadingIndicator v-else />
144+
</RouterView>
145+
</template>
146+
```
147+
148+
The trick here is that the `Component` in the slot props will be `undefined` if the route hasn't resolved yet.

docs/faq/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Only about half the questions have complete answers. Those questions are listed
5656
- [Can I use JavaScript classes for my reactive data?](reactivity-and-classes)
5757
- [Why does selecting one item select them all?](independent-selections)
5858
- [How do I create unique element ids with Vue?](unique-element-ids)
59-
- [Why can't I use the current route inside `App.vue`?](accessing-the-route)
59+
- [Why can't I use the current route in `App.vue`?](accessing-the-route)
6060

6161
---
6262

0 commit comments

Comments
 (0)