Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.8 KiB
4.8 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Per-Route beforeEnter Guards Ignore Param/Query Changes | MEDIUM | Route-level beforeEnter guards don't fire when only params, query, or hash change, causing unexpected bypasses of validation logic | gotcha |
|
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
onBeforeRouteUpdatefor param/query changes - Or use global
beforeEachwith route.params/query checks - Document which guards protect which scenarios
- Test navigation between same route with different params
The Problem
// 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:
- User navigates from
/productsto/orders/1- beforeEnter runs, access checked - User navigates from
/orders/1to/orders/2- beforeEnter DOES NOT run! - 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
<!-- OrderDetail.vue -->
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
// Handle param changes within the same route
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
const order = await checkOrderAccess(to.params.id)
if (!order.canView) {
return '/unauthorized'
}
}
})
</script>
Solution 2: Use Global beforeEach Instead
// 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
// 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
// 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
- beforeEnter is for route ENTRY only - Not for within-route changes
- Use onBeforeRouteUpdate for param changes - This is the in-component solution
- Global beforeEach always runs - Good for centralized validation
- Test param change scenarios - Easy to miss during development
- Consider security implications - Param-based access control needs both guards