|
| 1 | +## Core |
| 2 | + |
| 3 | +- routes: tuples like `['/rooms/:id', RouteModule]` (and/or route groups with `layout` + shared `loader`) |
| 4 | +- route hooks (`loader`, `param_rules`, `search_schema`, `search_options`, `before_route_leave`) live in the route file’s `<script module>` |
| 5 | +- navigation: match -> leave-guard (`before_route_leave`, can `nav.cancel()`) -> loaders -> update stores -> `after_navigate(nav)` |
| 6 | +- `before_navigate(nav)` runs after matching (so `nav.to` includes `url/route/params/matches`) and can cancel; it does not run for the initial `init()` navigation |
| 7 | +- `goto()` and `preload()` are safe to call without `await` from events (no unhandled rejections); errors surface via `nav.to.data.__error` (or preload bundle `data.__error`) |
| 8 | +- `router.init()` attaches the router instance to `window.navgo` by default |
| 9 | +- stores: |
| 10 | + - `window.navgo.route`: `{ url, route, params, matches, search_params }` |
| 11 | + - `window.navgo.is_navigating`: boolean |
| 12 | + |
| 13 | +## Setup (App Wiring) |
| 14 | + |
| 15 | +- `const router = new Navgo(routes, { after_navigate })`; `await router.init()` once at startup |
| 16 | +- `after_navigate(nav)` sets app props: `route_data = nav.to?.data ?? null`, `is_404 = nav.to?.data?.__error?.status === 404`, `Component = leaf route module default` |
| 17 | +- render `Component` keyed by `$route.url.pathname`, pass `data={route_data}` |
| 18 | + |
| 19 | +## Patterns / Usage |
| 20 | + |
| 21 | +- Search params: |
| 22 | + - add `search_schema` (route or group) so `window.navgo.search_params` stays typed/defaulted |
| 23 | + - update via `window.navgo.search_params.update(o => ({ ...o, q: 'x', page: 1 }))` (shallow URL update; loaders are not re-run) |
| 24 | + - common pattern: loader uses `search_params` for initial data, then a `$effect` refetches when `$search_params` changes |
| 25 | +- Loaders: |
| 26 | + - returning a non-Promise object means a LoadPlan (cached fetch plan), not plain data; return plain object data via `async loader()` |
| 27 | + - layout/group loaders run outer → inner and their results are on `nav.to.matches[*].data` (leaf convenience stays on `nav.to.data`) |
| 28 | + - SWR revalidation can update `nav.to.data` after navigation; subscribe via `after_navigate(nav, on_revalidate)` |
| 29 | + - Avoid these common mistakes: |
| 30 | + - Re-fetching the same data in the component `<script>` that the loader already fetched (double network + out-of-sync state). Prefer using the `data` prop. |
| 31 | + - Overcomplicating the loader: keep it as a LoadPlan (just URLs/specs). Do client-side orchestration in the component only if it truly depends on interactive state. |
| 32 | + - Duplicating imports across `<script>` and `<script module>`: in Svelte they share imports; duplicating can cause compile errors. Put imports in one place. |
| 33 | +- Shallow history: |
| 34 | + - `push_state`/`replace_state` updates URL/state without re-running loaders |
| 35 | + - on Back/Forward, an entry is only treated as shallow when it was created from the currently-rendered pathname (`state.__navgo.from`) |
| 36 | +- Param handling: |
| 37 | + - `param_rules`: schema then coercer; coercer/validate exceptions skip the route (mismatch) |
| 38 | +- Scroll model: |
| 39 | + - `history.state.__navgo.idx` drives restoration; on `popstate` restores `window` + `[data-scroll-id]/#id` when known, otherwise falls through to hash/top; divergent history clears forward scroll snapshots |
| 40 | +- `goto` usage: |
| 41 | + - prefer plain `<a href="/path">` |
| 42 | + - use `window.navgo.goto('/path')` for buttons/menus/command palette |
| 43 | + |
| 44 | +## Recipes / Common Scenarios |
| 45 | + |
| 46 | +- Auth gate (block entry): |
| 47 | + - in `before_navigate(nav)`: `if (!session && nav.to?.route?.[0].startsWith('/admin')) nav.cancel()` |
| 48 | +- Unsaved-changes guard (block leaving current page): |
| 49 | + - in `before_route_leave(nav)`: `if (dirty && (nav.type === 'link' || nav.type === 'goto')) nav.cancel()` |
| 50 | +- Filters in URL without reloading: |
| 51 | + - define `search_schema`; update via `$search_params = { ...$search_params, q, page: 1 }` (shallow URL update) |
| 52 | +- SWR revalidate refresh: |
| 53 | + - `after_navigate(nav, on_revalidate) { render(nav.to?.data); on_revalidate?.(() => render(nav.to?.data)) }` |
| 54 | +- Handle 404 / loader errors: |
| 55 | + - `const err = nav.to?.data?.__error; if (err?.status === 404) show_404 = true` |
| 56 | +- Shared layout data: |
| 57 | + - read `nav.to.matches` outer → inner; layouts are `m.type === 'layout'`, route leaf is `m.type === 'route'` |
| 58 | +- Stable scroll panes: |
| 59 | + - set `id="pane"` or `data-scroll-id="pane"` on scroll containers to get popstate restoration |
0 commit comments