"" (empty string) -> ROUTE_NOT_FOUND via Promise rejection
null/undefined/number -> TypeError via Promise rejection
Non-existent route -> ROUTE_NOT_FOUND via Promise rejection + TRANSITION_ERROR event
params?: Params (optional)
Purpose: Route parameters (substituted in URL)
Allowed values: Object with string keys and values: string | number | boolean | null | array
Default value: {}
Error behavior:
null/undefined -> interpreted as {}
NaN, Infinity, -Infinity values in params -> Promise rejects with TypeError (not serializable)
undefined values in params are stripped.router.navigate("x", { a: 1, b: undefined }) produces the same URL and state.params as router.navigate("x", { a: 1 }) — the b key is absent from both. This contract is guaranteed by @real-router/core itself (not by the query-string engine), so it also holds when plugins inject undefined via forwardState interceptors.
null and "" are distinct, defined values — they are NOT stripped:
Successful result: Resolves with State — the new router state after navigation
Unsuccessful result: Rejects with RouterError
Special cases:
For ROUTER_NOT_STARTED, ROUTE_NOT_FOUND, SAME_STATES — Promise rejects, no events emitted for ROUTER_NOT_STARTED
For cancellation — starting a new navigation cancels the previous one; stop(), dispose(), or aborting an external AbortSignal also cancel in-flight transitions. The Promise rejects with TRANSITION_CANCELLED
5. Side Effects
State change: On successful transition router.setState(newState) is called
Event triggers (in order):
TRANSITION_START — before executing guards
TRANSITION_SUCCESS — after successful transition
TRANSITION_ERROR — on error (guard block, route not found, etc.)
TRANSITION_CANCEL — on transition cancellation
Browser impact: When using browser plugin — URL and history changes
6. Possible Errors
Condition
Error Code
Behavior
How to Handle
Router not started
ROUTER_NOT_STARTED
Promise rejects with error, no events
Check router.isActive()
Route not found
ROUTE_NOT_FOUND
Promise rejects + TRANSITION_ERROR event
Check route name
Current and target states equal
SAME_STATES
Promise rejects + TRANSITION_ERROR event
Use reload: true or force: true
Guard canActivate returned false
CANNOT_ACTIVATE
Promise rejects + TRANSITION_ERROR event
Check guard logic
Guard canDeactivate returned false
CANNOT_DEACTIVATE
Promise rejects + TRANSITION_ERROR event
Use forceDeactivate: true
Transition cancelled
TRANSITION_CANCELLED
Promise rejects + TRANSITION_CANCEL event
Expected with concurrent navigation, signal.abort(), stop(), or dispose()
Set not-found state synchronously (e.g., after API 404)
getLifecycleApi(router).addActivateGuard()
Register guard for route entry
getLifecycleApi(router).addDeactivateGuard()
Register guard for route exit
router.stop()
Stop router (cancels in-flight navigation)
router.dispose()
Permanently terminate router
8. Behavior
Main Scenarios
Successful navigation updates router.getState() and the returned Promise resolves with state
Navigation with parameters substitutes them in URL path: navigate("users.view", { id: 123 }) -> /users/view/123
Navigation to nested routes invokes guards of all segments in chain
With reload: true or force: true — transition executes even to current state
Route default params are extended with passed parameters
Edge Cases
Navigating from UNKNOWN_ROUTE: When the current state is UNKNOWN_ROUTE (set by navigateToNotFound()), navigate() automatically forces replace: true even if not explicitly passed. This prevents the browser history from accumulating not-found entries that the user would cycle through with the back button.
Parallel navigations: When starting new navigation during async transition — previous transition is cancelled (TRANSITION_CANCELLED) and previous navigation's internal AbortController is aborted
Calling router.stop() during guard: Transition is cancelled with TRANSITION_CANCELLED and internal AbortController is aborted
Pre-aborted signal: If opts.signal is already aborted, navigation rejects immediately without starting a transition
AbortError in guards: DOMException("AbortError") thrown in guards (e.g., from fetch()) is auto-converted to TRANSITION_CANCELLED
Promise in guard resolves after cancellation: Ignored (Promise not resolved/rejected again)
Removed — use state.transition?.redirected instead
Check redirect status via transition field
Migration Examples
Promise API replacing Callbacks (CRITICAL)
// Code that will break (router5 / old Real Router)router.navigate("profile",(err,state)=>{if(err){console.error("Navigation failed:",err.code);}else{console.log("Navigated to:",state.name);}});// Code after migration (Real Router)try{conststate=awaitrouter.navigate("profile");console.log("Navigated to:",state.name);}catch(err){console.error("Navigation failed:",err.code);}
CancelFn Removed (CRITICAL)
// Code that will break (router5 / old Real Router)constcancel=router.navigate("slow-page",{id: 1});// ... latercancel();// CancelFn no longer returned!// Code after migration (Real Router)// Option 1: Concurrent navigation cancels previousrouter.navigate("slow-page",{id: 1});router.navigate("other-page");// Previous promise rejects with TRANSITION_CANCELLED// Option 2: Cancel via AbortControllerconstcontroller=newAbortController();router.navigate("slow-page",{id: 1},{signal: controller.signal});controller.abort();// cancels this specific navigation// Option 3: Stop the router (cancels in-flight transition)router.navigate("slow-page",{id: 1});router.stop();// Option 4: Permanently terminate (cancels + prevents restart)router.dispose();
Redirects from Guards (CRITICAL)
import{getPluginApi}from"@real-router/core/api";// Code that will break (router5)router.canActivate("protected",()=>(toState,fromState,done)=>{if(!isAuthenticated){// In router5 this triggered navigation to logindone({redirect: {name: "login",params: {}}});return;}done();});// Code after migration (Real Router)// Use route config canActivate (ActivationFn) for redirectsconstroutes=[{name: "protected",path: "/protected",canActivate: (router)=>(toState,fromState)=>{if(!isAuthenticated){// ActivationFn can return State for redirectreturngetPluginApi(router).buildState("login");}returntrue;},},];
Guard Registration API (HIGH)
import{getLifecycleApi}from"@real-router/core/api";// Code that will break (router5 / old Real Router)router.canActivate("route",guardFn);router.canDeactivate("route",guardFn);// Code after migration (Real Router)constlifecycle=getLifecycleApi(router);lifecycle.addActivateGuard("route",guardFn);lifecycle.addDeactivateGuard("route",guardFn);
transitionToState Method (HIGH)
import{getPluginApi}from"@real-router/core/api";// Code that will break (router5)router.transitionToState(toState,fromState,opts,callback);// Code after migration (Real Router)constpluginApi=getPluginApi(router);conststate=awaitpluginApi.navigateToState(toState,fromState,opts);// Promise-based, no callback or emitSuccess parameter
Options Validation (MEDIUM)
// Code that will break (Real Router)router.navigate("users",{},{replace: "true"});// TypeError!router.navigate("users",{},"options");// TypeError!// Correct coderouter.navigate("users",{},{replace: true});// boolean, not string
Migration Checklist
Replace callback-based navigate with async/await and try/catch
Replace CancelFn pattern (const cancel = router.navigate(...)) — use concurrent navigation, stop(), or dispose()
Remove redirects from guards — use route config canActivate (ActivationFn can return State)
Replace canActivate with getLifecycleApi(router).addActivateGuard()
Replace canDeactivate with getLifecycleApi(router).addDeactivateGuard()
Replace transitionToState with getPluginApi(router).navigateToState()
Check options types — replace, reload, force must be boolean
Check params for NaN/Infinity — will be rejected
Update error handling — err is now RouterError (but err.code works)