Troubleshooting
This guide covers common configuration and runtime issues with Udodi Router. Each section describes the symptom, likely causes, and how to resolve them.
Route matching
No route matches the current URL
Symptoms
- Navigation returns an
errorresult. router.route.errorindicates that no route matched.- Server-side
matchRoutes()orresolveHead()returnsnull.
Common causes
- The path is missing a leading
/or contains a query string or hash. - The application is deployed under a subpath but
baseis not configured. - The catch-all route (
*) is missing or not declared at the root level. - A constrained parameter rejects the segment (for example
:id(\\d+)with a non-numeric value).
What to check
- Route paths must be pathname-only and application-relative. Do not include
?or#. - Configure
basewhen the app is served under a prefix such as/app. - Ensure a root-level
{ path: "*", component: NotFound }exists if you want a fallback. - Verify constrained parameters match the actual URL segments.
See Routes for path syntax and matching priority.
Static route loses to a parameterized route
Symptoms
/users/newis handled by/users/:idinstead of a static/users/newroute.
Cause
Udodi Router prefers static matches over parameterized ones, so this usually indicates a path normalization or declaration issue rather than specificity failure.
What to check
- Confirm both routes are declared correctly and normalize to distinct patterns.
- Remember that trailing slashes are stripped (except for
/). - Duplicate patterns are rejected at compile time; inspect the error thrown during
createRouter().
Index route does not render
Symptoms
- Navigating to the parent URL shows the layout but no child content.
Cause
The parent has no index child (path: ""), so the nested RouterView has nothing to render at that depth.
Solution
Add an index child:
{
path: "/dashboard",
component: DashboardLayout,
children: [
{ path: "", component: DashboardHome },
{ path: "settings", component: DashboardSettings },
],
}See Nested Routes.
Navigation
push() / replace() never resolves as completed
Symptoms
- Result type is
cancelled,redirected, orerror.
What to check
| Result | Likely cause |
|---|---|
cancelled | A guard returned false, or a newer navigation superseded this one. |
redirected | A guard returned a string or { path } target. |
error | Invalid target, no match, loader failure, lazy-load failure, or redirect loop. |
Always inspect result.type and, for errors, result.error.
See Navigation and Guards and Loaders.
Redirect loop
Symptoms
- Navigation fails with a redirect-loop error.
Cause
Guards continually redirect to targets that redirect back, or the redirect chain exceeds the internal limit.
Solution
- Ensure at least one destination can complete without redirecting.
- Avoid unconditional redirects between
/loginand a protected route. - Log the
redirectTovalues while debugging.
Navigation appears to complete but the URL does not change
Symptoms
- Route state updates, but the address bar stays the same (or the opposite).
Common causes
- Using
replace()when you expectedpush(). - History adapter and router
baseare inconsistent. - Hash history is configured but you are looking at the pathname instead of the fragment.
What to check
- Confirm which history adapter is in use.
- Keep
baseconsistent betweencreateRouter({ base })and the history adapter. - For hash mode, the application path lives in the fragment (
/#/about).
See History and Scroll.
Links and outlets
Link click does a full page reload
Symptoms
- Clicking a
router.Link()causes a full document navigation.
Common causes
- The click was modified (Ctrl/Cmd/Shift/Alt) or used a non-primary button.
- The anchor has
download,targetother than_self, orrel="external". - The template root is not an element that receives the click handler as expected.
What to check
- Ordinary left-clicks on anchors are intercepted; modified clicks intentionally use native behavior.
- Prefer letting the router set
hrefrather than supplying your own conflicting attributes.
See Links and Outlets.
isActive never becomes true
Symptoms
- Active classes never apply, or the wrong link stays active.
Common causes
- Missing or incorrect
exactoption for an index/overview link. - The
topath does not match the declared route path (including base handling). - Reading a non-reactive snapshot of active state outside an effect or template.
What to check
- Use
exact: truewhen a parent path should not stay active for nested children. - Prefer the
isActivevalue provided inside theLinktemplate context. - For manual checks, call
router.isActive()inside aneffector computed so it tracksactiveVersion.
Nested RouterView is empty
Symptoms
- Parent layout renders, but the nested outlet shows nothing.
Common causes
- No child matched at that depth (missing index route or wrong child path).
- Outlet depth does not match the matched hierarchy.
- Absolute child path still renders under the parent layout by design.
What to check
- Confirm the matched chain with
router.route.matched. - Ensure each layout that should show a child contains its own
router.RouterView(). - If a child should render outside the parent layout, declare it as a root-level route.
See Nested Routes.
Guards and loaders
Loader runs but data is missing in the component
Symptoms
- Component reads
router.route.dataand seesundefinedor stale values.
Common causes
- Reading data during preparation (before commit). While
pendingistrue, route fields still describe the previous route. - Expecting parent loader data on
router.route.data(that property is the leaf only). - Loader threw; navigation failed and the previous route remained active.
What to check
- Subscribe reactively to
router.route.dataafter commit. - For parent data, inspect
router.route.matched[i].data. - Handle
result.type === "error"androuter.route.error.
Guard never runs
Symptoms
- Unauthenticated users reach a protected page.
Common causes
- Guard is on a child but navigation matched a different branch.
- Guard returned
undefined/trueunintentionally. - Client-side guard is treated as a security boundary (it is not).
What to check
- Place shared conditions on the parent that always matches the protected subtree.
- Return
falseto cancel or a target object/string to redirect. - Enforce authorization on the server as well.
Lazy loading
Lazy page fails with a module error
Symptoms
- Navigation returns
error; console shows import or chunk failures.
Common causes
- Incorrect import path or missing file in the deployment.
- Named export not unwrapped correctly.
- Stale HTML referencing an old chunk after a new deploy.
What to check
// Default export
loadComponent: () => import("./pages/Reports.js")
// Named export
loadComponent: () =>
import("./pages/Reports.js").then(({ Reports }) => Reports)- Verify the deployed assets match the import URLs.
- A failed load is removed from the cache; the next navigation can retry.
See Lazy Loading.
Loading indicator never appears
Symptoms
- User sees the previous page with no feedback during a slow import.
Cause
Lazy resolution happens before commit. The previous route stays active until preparation succeeds.
Solution
Reflect router.route.pending in application UI:
effect(() => {
ctx.showSpinner = router.route.pending;
});History, base, and scroll
Direct refresh returns 404
Symptoms
- Client navigation works, but refreshing
/aboutfails on the server.
Cause
HTML5 history requires the server to serve the application entry HTML for client routes.
Solutions
- Configure server fallback to
index.html(or equivalent). - Or switch to hash history:
createRouter({
routes,
history: createHashHistory(),
});Links point to the wrong path under a subpath deployment
Symptoms
- App is hosted at
/appbut links navigate to/aboutinstead of/app/about.
Cause
Missing or inconsistent base.
Solution
const base = "/app";
createRouter({
routes,
base,
history: createWebHistory({ base }),
});Keep router base and the history adapter base aligned.
Scroll jumps or does not restore on Back/Forward
Symptoms
- Scroll position is wrong after history traversal, or native restoration races with the app.
What to check
- Provide a
scrollBehaviorthat returnssavedPositionwhen available. - When
scrollBehavioris set, the router enables manual scroll restoration; ensure your policy covers the cases you care about. - Fragment targets must exist in the DOM when the callback runs.
- Nested scroll containers are not managed automatically.
See History and Scroll.
Metadata and scripts
Title or meta tags do not update
Symptoms
- Document title stays on the previous page's value.
Common causes
- Metadata is defined only on a parent that is not in the matched chain.
- Navigation failed before commit, so metadata was not applied.
- A child clears a field with
nullunintentionally.
What to check
- Inspect
router.route.metaafter a successful navigation. - Confirm inheritance rules: omit inherits, override replaces,
nullclears. - Head updates run after commit; failed navigations leave the previous head state.
See Metadata.
Analytics fires twice on every navigation
Symptoms
- Double
page_viewevents in Google Analytics.
Cause
Both the analytics bootstrap and the router report page views.
Solution
Initialize gtag with send_page_view: false and let the router report after commits. Avoid a second independent page-view handler for the same navigation.
Script with once: false does not re-run side effects as expected
Symptoms
- Re-entering a route reinserts the script node, but global state or listeners remain from the previous run.
Cause
Removing a script element does not undo JavaScript that has already executed.
Guidance
Prefer once: true for long-lived services. Use once: false only when reinsertion is specifically required by the integration.
Server-side SEO
resolveHead() returns empty head
Symptoms
matchisnullorheadis an empty string.
Common causes
- Pathname includes query/hash.
- Base path mismatch.
- No matching route and no catch-all.
What to check
- Pass pathname only:
url.pathname. - Supply
queryseparately when loaders need it. - Configure
baseconsistently with the public URL structure.
Dynamic metadata is missing loader data on the server
Symptoms
- Titles fall back to defaults even though a loader exists.
Common causes
runLoaders: falsewithout providingloaderData.- Loader data keyed incorrectly (prefer a
Mapof route records). - Loader threw and the error was not handled at the request boundary.
What to check
- Keep
runLoaders: true(default) unless you intentionally supply preloaded data. - Use a
Mapkeyed by the actual route-record objects when possible.
See Server-Side SEO.
General debugging tips
- Inspect navigation results: Always check
result.typefrompush()/replace()and the one-timerouter.readypromise. - Watch reactive state:
router.route.pending,router.route.error,router.route.matched, androuter.route.metaare the primary signals after commit. - Compile-time errors: Duplicate patterns, invalid constraints, and malformed route records throw during
createRouter(). Read the full error message. - Keep bases consistent: Router
base, history adapterbase, and server matcherbaseshould describe the same deployment prefix. - Client guards are not security: Protect data and actions on the server regardless of
beforeEnteroutcomes.
Related guides
- Routes — Path syntax and matching
- Navigation — Results, redirects, and cancellation
- Guards and Loaders — Preparation failures
- Lazy Loading — Component resolution errors
- History and Scroll — Base paths and scroll behavior
- Metadata — Head updates and inheritance
- Server-Side SEO — Server matching and head generation