---
name: vue-router-registration-async-store
description: Vue Router must be registered with app.use(router) AFTER await authStore.init() — registering before the store initializes causes navigation guards to run before auth state is ready
type: concept
---

# Vue Router — Registration Order vs Async Store Init

## The Problem

Vue apps with authentication typically have an async init step (`authStore.init()`) that:
- Reads the stored token
- Validates/refreshes it
- Sets `isAuthenticated`

If `app.use(router)` runs **before** this init completes, the first navigation guard fires before `isAuthenticated` is known → guard sees unauthenticated state → redirects to `/login` even for authenticated users.

```ts
// WRONG — router registers before auth state is resolved
const app = createApp(App)
app.use(router)  // ← guard runs immediately, isAuthenticated = false
app.use(pinia)

await authStore.init()  // ← too late
app.mount('#app')
```

## Correct Order

```ts
// CORRECT
const app = createApp(App)
app.use(pinia)

const authStore = useAuthStore()
await authStore.init()  // ← auth state resolved first

app.use(router)  // ← now guards run with correct isAuthenticated
app.mount('#app')
```

## Navigation Guard Pattern

```ts
// router/index.ts
router.beforeEach((to, _from, next) => {
  const auth = useAuthStore()

  if (to.meta.public) {
    // Public route — but redirect authenticated users away from login
    if (auth.isAuthenticated && to.name === 'Login') {
      return next({ name: 'Dashboard' })
    }
    return next()
  }

  // Protected route
  if (!auth.isAuthenticated) {
    return next({ name: 'Login', query: { redirect: to.fullPath } })
  }

  next()
})
```

## The `meta.public` Bypass Gotcha

`meta.public = true` marks a route as publicly accessible.

**But:** authenticated users hitting the login page should be redirected to the app.
The guard must explicitly check `auth.isAuthenticated` even on `meta.public` routes for this redirect to work.

Without the check: authenticated user navigates to `/login` → page renders → jarring UX.

## Related

- [[wiki/concepts/zustand-async-hydration]] — same pattern in React/Zustand (gate on hasHydrated)
- [[wiki/concepts/axios-401-interceptor-infinite-loop]] — auth refresh loop prevention

**Source:** daily/2026-05-13.md | 2026-05-13
