Navigation guards in @esmx/router provide hooks into the route transition process, allowing you to control navigation, perform checks, redirect users, or execute side effects at different stages of a route change.
type RouteConfirmHook = (
to: Route,
from: Route | null,
router: Router
) => Awaitable<RouteConfirmHookResult>;A confirmation guard that can approve, reject, or redirect navigation. Used by beforeEach() and beforeEnter.
type RouteConfirmHookResult =
| void
| false
| RouteLocationInput
| RouteHandleHook;Return values for confirmation guards:
void or undefined: Allow navigation to proceedfalse: Cancel the navigationRouteLocationInput: Redirect to a different locationRouteHandleHook: Provide a custom handle function for the routetype RouteNotifyHook = (
to: Route,
from: Route | null,
router: Router
) => void;A notification hook called after navigation completes. Cannot cancel or redirect navigation. Used by afterEach().
type RouteVerifyHook = (
to: Route,
from: Route | null,
router: Router
) => Awaitable<boolean>;A verification hook that returns a boolean. Used in layer keep-alive logic.
type RouteHandleHook = (
to: Route,
from: Route | null,
router: Router
) => Awaitable<RouteHandleResult>;A handle hook for custom route handling logic. Can only be called once per navigation.
type RouteHandleResult = unknown | null | void;Return type of handle hooks. The result is accessible via route.handleResult.
guard: RouteConfirmHook — Guard function() => void — Unregister functionRegisters a global guard called before every navigation. Guards are called in registration order. If any guard cancels or redirects, subsequent guards are skipped.
// Authentication guard
const unregister = router.beforeEach((to, from, router) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
return '/login';
}
});
// Permission guard
router.beforeEach((to, from, router) => {
if (to.meta.role && !hasRole(to.meta.role)) {
return false; // Cancel navigation
}
});guard: RouteNotifyHook — Hook function() => void — Unregister functionRegisters a global hook called after every navigation completes. After-each hooks cannot affect navigation — they are purely for side effects like analytics, title updates, or logging.
router.afterEach((to, from, router) => {
// Update page title
document.title = to.meta.title || 'My App';
// Track page view
analytics.pageView(to.path);
});RouteConfirmHookGuard called before entering a specific route. Defined in the route configuration.
const routes = [
{
path: '/admin',
component: AdminPanel,
beforeEnter: (to, from, router) => {
if (!isAdmin()) {
return '/unauthorized';
}
}
}
];RouteConfirmHookGuard called when the route is reused but parameters change (e.g., navigating from /user/1 to /user/2).
{
path: '/user/:id',
component: UserDetail,
beforeUpdate: (to, from, router) => {
// Called when :id changes
console.log('User changed from', from?.params.id, 'to', to.params.id);
}
}RouteConfirmHookGuard called before leaving a specific route. Useful for preventing navigation when there are unsaved changes.
{
path: '/editor',
component: Editor,
beforeLeave: (to, from, router) => {
if (hasUnsavedChanges()) {
const confirmed = window.confirm('You have unsaved changes. Leave?');
if (!confirmed) return false;
}
}
}When a navigation occurs, guards are executed in the following order:
// Example showing execution order
const router = new Router({
routes: [
{
path: '/a',
component: A,
beforeLeave: () => console.log('1. beforeLeave /a'),
beforeEnter: () => console.log('4. beforeEnter /a')
},
{
path: '/b',
component: B,
beforeEnter: () => console.log('4. beforeEnter /b')
}
]
});
router.beforeEach(() => console.log('2-3. global beforeEach'));
router.afterEach(() => console.log('6. global afterEach'));
// Navigate from /a to /b:
// 1. beforeLeave /a
// 2-3. global beforeEach
// 4. beforeEnter /b
// (navigation executes)
// 6. global afterEachAll guard registration methods return an unregister function. Call it to remove the guard:
const unregister = router.beforeEach((to) => {
// Guard logic
});
// Later, remove the guard
unregister();This is especially important in component-based frameworks where guards registered in component lifecycle hooks should be cleaned up when the component unmounts.