
🏰 What is Citadel?
Vue Router Citadel is a middleware-driven navigation control system for Vue Router 4 & 5 that lets you build layered, predictable, and scalable route protection.
Where Vue Router gives you guards at the entrance, Citadel introduces navigation outposts — internal checkpoints that control access, preload data, enforce permissions, and orchestrate complex navigation flows.
Think of it as turning your router into a fortress.
🧱 The Fortress Philosophy
Multiple layers of control — just like a real fortress.
🏰 Citadel → ✋ Outposts (🛡 Guards) → 📍 Final point✨ Designed for Scalable Apps
Access Control:
- RBAC systems — role checks, permission gates, admin areas
- Multi-tenant apps — tenant validation, subscription tiers, feature flags
Architecture:
- Large-scale modular apps — type-safe declarations per module (advanced patterns)
- Dynamic management — deploy/abandon outposts and assign/revoke to routes at runtime (dynamic management)
Navigation Logic:
- Complex auth flows — SSO, MFA, session refresh, token validation
- Data preloading — fetch data before navigation completes
🔄 Before & After
As an application grows, navigation logic tends to scatter across multiple beforeEach calls — each one independent, unordered, and hard to reason about:
Before — scattered guards:
// auth.ts
router.beforeEach((to) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
return { name: 'login' };
}
});
// roles.ts
router.beforeEach((to) => {
if (to.meta.role && getUser().role !== to.meta.role) {
return { name: 'forbidden' };
}
});
// onboarding.ts
router.beforeEach((to) => {
if (to.meta.requiresOnboarding && !getUser().completedRegistration) {
return { name: 'onboarding' };
}
});
// analytics.ts
router.afterEach((to) => {
trackPageView(to.path);
});No guaranteed execution order, no error handling, no timeouts, guards don't know about each other, removing a guard at runtime requires manual bookkeeping.
After — unified citadel:
const citadel = createNavigationCitadel(router, {
outposts: [
{ name: 'auth', priority: 10, handler: authHandler },
{ name: 'role-access', scope: 'route', priority: 20, handler: roleHandler },
{ name: 'onboarding', scope: 'route', handler: onboardingHandler },
{ name: 'analytics', hooks: ['afterEach'], handler: analyticsHandler },
],
defaultTimeout: 5000,
onError: ({ error }) => {
reportError(error);
return verdicts.BLOCK;
},
});One entry point, explicit priority, scoped activation, timeout protection, structured error handling — all in a single declaration.
🔑 Key Concepts
Outpost
A navigation outpost is a named, prioritized handler that runs during navigation. Each outpost receives context (route info, router instance) and returns a verdict: allow, block, or redirect.
Scope
Outposts come in two scopes:
- Global — runs on every navigation
- Route — runs only when referenced in
route.meta.outposts
Verdict
Instead of next() callbacks, outposts return values:
verdicts.ALLOW— continue navigationverdicts.BLOCK— cancel navigationRouteLocationRaw— redirect to another route
Priority
Lower number = earlier execution. This gives you fine-grained control over processing order.
🚀 Next Steps
- Getting Started — install and set up your first outpost
- Comparison — how Citadel compares to alternatives