Skip to content

Navigation guards

A route can declare a beforeLoad guard. Guards run root → leaf over the matched chain before any component commits, so a blocked or redirected navigation never flashes the target UI.

Signature

ts
type 
Awaitable
<
T
> =
T
|
Promise
<
T
>;
interface GuardContext {
params
:
Record
<string, string>;
url
: URL;
pathname
: string;
/** Aborts when this navigation is superseded by a newer one. */
signal
: AbortSignal;
navigationType
: "reload" | "push" | "replace" | "traverse";
} type
BeforeLoad
= (
ctx
: GuardContext) =>
Awaitable
<void | boolean | string>;

Return values

ReturnEffect
undefined / trueAllow the navigation.
falseBlock — the current URL is restored.
stringRedirect (via replace) to that same-origin path.
ts
const 
router
=
createCoreRouter
([
{
path
: "/dashboard",
component
:
Dashboard
,
beforeLoad
: () => {
if (!
isLoggedIn
()) return "/login"; // redirect
}, }, ] as
const
);

Same-origin only

A redirect string must resolve to the same origin. If a guard returns a cross-origin target (including protocol-relative //host or javascript: URLs), the router throws instead of navigating and the snapshot moves to status: "error" — this closes the open-redirect class where a user-derived ?next= could send visitors to an external site. Same-origin and relative paths are unaffected.

Async guards & the abort signal

Guards can be async. Because navigations can be superseded (the user clicks another link mid-flight), the context carries an AbortSignal that fires when this navigation is no longer the latest. Forward it to fetch and bail out on abort:

ts
const 
router
=
createCoreRouter
([
{
path
: "/profile/:id",
component
:
Profile
,
beforeLoad
: async (
ctx
) => {
const
res
= await
fetch
(`/api/users/${
ctx
.
params
.
id
}`, {
signal
:
ctx
.
signal
,
}); if (
res
.
status
=== 404) return false; // block: restore current URL
}, }, ] as
const
);

TIP

Guards run root → leaf: a parent layout's guard runs before its children's. This makes the parent the natural place for shared auth checks — children only add what's specific to them.

Errors

If a guard throws (or a lazy import rejects), the snapshot transitions to status: "error" and snapshot.error holds the thrown value. The adapters render their error slot/prop for it — see the framework guides. You can also observe errors globally via the onError option.

Released under the MIT License.