--- title: Per-Route beforeEnter Guards Ignore Param/Query Changes impact: MEDIUM impactDescription: Route-level beforeEnter guards don't fire when only params, query, or hash change, causing unexpected bypasses of validation logic type: gotcha tags: [vue3, vue-router, navigation-guards, params, query] --- # Per-Route beforeEnter Guards Ignore Param/Query Changes **Impact: MEDIUM** - The `beforeEnter` guard defined in route configuration only triggers when entering a route from a DIFFERENT route. Changes to params, query strings, or hash within the same route do NOT trigger `beforeEnter`, potentially bypassing important validation logic. ## Task Checklist - [ ] Use in-component `onBeforeRouteUpdate` for param/query changes - [ ] Or use global `beforeEach` with route.params/query checks - [ ] Document which guards protect which scenarios - [ ] Test navigation between same route with different params ## The Problem ```javascript // router.js const routes = [ { path: '/orders/:id', component: OrderDetail, beforeEnter: async (to, from) => { // This runs when entering from /products // But NOT when navigating from /orders/1 to /orders/2! const order = await checkOrderAccess(to.params.id) if (!order.canView) { return '/unauthorized' } } } ] ``` **Scenario:** 1. User navigates from `/products` to `/orders/1` - beforeEnter runs, access checked 2. User navigates from `/orders/1` to `/orders/2` - beforeEnter DOES NOT run! 3. User might access order they don't have permission for! ## What Triggers beforeEnter vs. What Doesn't | Navigation | beforeEnter fires? | |------------|-------------------| | `/products` → `/orders/1` | YES | | `/orders/1` → `/orders/2` | NO | | `/orders/1` → `/orders/1?tab=details` | NO | | `/orders/1#section` → `/orders/1#other` | NO | | `/orders/1` → `/products` → `/orders/2` | YES (leaving and re-entering) | ## Solution 1: Add In-Component Guard ```vue ``` ## Solution 2: Use Global beforeEach Instead ```javascript // router.js router.beforeEach(async (to, from) => { // Handle all order access checks globally if (to.name === 'OrderDetail') { // This runs on EVERY navigation to this route, including param changes const order = await checkOrderAccess(to.params.id) if (!order.canView) { return '/unauthorized' } } }) ``` ## Solution 3: Combine Both Guards ```javascript // router.js - For entering from different route const routes = [ { path: '/orders/:id', component: OrderDetail, beforeEnter: (to) => validateOrderAccess(to.params.id) } ] // In component - For param changes within route // OrderDetail.vue onBeforeRouteUpdate((to) => validateOrderAccess(to.params.id)) // Shared validation function async function validateOrderAccess(orderId) { const order = await checkOrderAccess(orderId) if (!order.canView) { return '/unauthorized' } } ``` ## Solution 4: Use beforeEnter with Array of Guards ```javascript // guards/orderGuards.js export const orderAccessGuard = async (to) => { const order = await checkOrderAccess(to.params.id) if (!order.canView) { return '/unauthorized' } } // router.js const routes = [ { path: '/orders/:id', component: OrderDetail, beforeEnter: [orderAccessGuard] // Can add multiple guards } ] // Still need in-component guard for param changes! ``` ## Full Navigation Guard Execution Order Understanding when each guard type fires: ``` 1. beforeRouteLeave (in-component, leaving component) 2. beforeEach (global) 3. beforeEnter (per-route, ONLY when entering from different route) 4. beforeRouteEnter (in-component, entering component) 5. beforeResolve (global) 6. afterEach (global, after navigation confirmed) For param/query changes on same route: 1. beforeRouteUpdate (in-component) - ONLY this fires! 2. beforeEach (global) 3. beforeResolve (global) 4. afterEach (global) ``` ## Key Points 1. **beforeEnter is for route ENTRY only** - Not for within-route changes 2. **Use onBeforeRouteUpdate for param changes** - This is the in-component solution 3. **Global beforeEach always runs** - Good for centralized validation 4. **Test param change scenarios** - Easy to miss during development 5. **Consider security implications** - Param-based access control needs both guards ## Reference - [Vue Router Navigation Guards](https://router.vuejs.org/guide/advanced/navigation-guards.html) - [Vue Router Per-Route Guards](https://router.vuejs.org/guide/advanced/navigation-guards.html#per-route-guard)