Router
Ginjou can work with any router tool.
This layer gives Ginjou one shared router contract for navigation, path resolution, and current location state.
Router Context
Router context is the shared entry point for navigation and route state.
Interface
interface Router {
go: (params: RouterGoParams<any>) => void
back: () => void
resolve: (params: RouterGoParams<any>) => string
getLocation: () => RouterLocation<any>
onChangeLocation: (handler: (location: RouterLocation<any>) => void) => () => void
blocker?: (
shouldBlock: (input: {
currentLocation: RouterLocation<any>
nextLocation: RouterLocation<any> | undefined
}) => boolean
) => RouterBlockerController
}
Methods
| Method | What it does |
|---|---|
go | Navigate to the next location. |
back | Move one step backward in router history. |
resolve | Convert route params into a final path string or href. |
getLocation | Return the current location snapshot. |
onChangeLocation | Subscribe to location changes and return a cleanup function. |
blocker | Optionally register a callback that can hold a navigation. See Blocker. |
Navigate
useGo() is the low-level navigation helper.
It forwards one RouterGoParams object to the current router context.
If no router context exists, the helper becomes a safe no-op.
interface RouterGoParams<TMeta = unknown> {
to?: string
type?: 'push' | 'replace'
query?: Record<string, string | number | null | undefined>
hash?: string
keepHash?: boolean
keepQuery?: boolean
meta?: TMeta
}
| Property | Meaning |
|---|---|
to | The target path when you navigate by raw path. |
type | Choose push or replace. |
query | Query values for the next location. |
hash | Hash value for the next location. |
keepHash | Reuse the current hash when hash is not passed. |
keepQuery | Merge the current query into the next query before applying query. |
meta | Adapter-specific navigation options. |
With the official vue-router bridge, meta can carry vue-router route options such as named-route params.
keepQuery and keepHash reuse values from the current location. They do not create a second navigation step.
<script setup lang="ts">
import { useGo } from '@ginjou/vue'
const go = useGo()
function openPosts() {
go({
to: '/posts',
query: {
page: 2,
status: 'published',
},
keepHash: true,
})
}
function openPost(id: number) {
go({
meta: {
name: 'post-show',
params: { id },
},
})
}
</script>
<script lang="ts">
import { useGo } from '@ginjou/svelte'
const go = useGo()
function openPosts() {
go({
to: '/posts',
query: {
page: 2,
status: 'published',
},
keepHash: true,
})
}
function openPost(id: number) {
go({
meta: {
name: 'post-show',
params: { id },
},
})
}
</script>
Go Back
useBack() maps to router.back().
Use it when the page should follow the router history instead of targeting a new path.
If no router context exists, the helper becomes a safe no-op.
<script setup lang="ts">
import { useBack } from '@ginjou/vue'
const back = useBack()
</script>
<template>
<button type="button" @click="back()">
Back
</button>
</template>
<script lang="ts">
import { useBack } from '@ginjou/svelte'
const back = useBack()
</script>
<button type="button" onclick={back}>
Back
</button>
Current Location
useLocation() returns the current location as a reactive ref.
It starts from router.getLocation() and updates when the router context emits location changes.
interface RouterLocation<TMeta = unknown> {
path: string
params?: Record<string, string | string[]>
query?: Record<string, string | null | Array<string | null>>
hash?: string
meta?: TMeta
}
| Property | Meaning |
|---|---|
path | The current pathname. |
params | Decoded params extracted from the current path. |
query | The current query object. |
hash | The current hash value. |
meta | Adapter-specific parsed location metadata. |
<script setup lang="ts">
import { useLocation } from '@ginjou/vue'
import { computed } from 'vue'
const location = useLocation()
const currentPage = computed(() => location.value?.query?.page)
const currentId = computed(() => location.value?.params?.id)
</script>
<template>
<div>
<p>Path: {{ location?.path }}</p>
<p>Page: {{ currentPage }}</p>
<p>ID: {{ currentId }}</p>
</div>
</template>
<script lang="ts">
import { useLocation } from '@ginjou/svelte'
const location = useLocation()
const currentPage = $derived(location.value?.query?.page)
const currentId = $derived(location.value?.params?.id)
</script>
<div>
<p>Path: {location.value?.path}</p>
<p>Page: {currentPage}</p>
<p>ID: {currentId}</p>
</div>
Resolve Paths
useResolvePath() turns router params into a final path string.
It uses the same input shape as useGo(), but returns the resolved path instead of navigating.
This is useful for href values, custom links, previews, or anywhere else you need the final string before navigation.
<script setup lang="ts">
import { useResolvePath } from '@ginjou/vue'
import { computed } from 'vue'
const resolvePath = useResolvePath()
const postsHref = computed(() => resolvePath({
to: '/posts',
query: {
page: 2,
},
}))
const showHref = computed(() => resolvePath({
meta: {
name: 'post-show',
params: {
id: 42,
},
},
}))
</script>
<template>
<a :href="postsHref">Posts</a>
<a :href="showHref">Open Post</a>
</template>
<script lang="ts">
import { useResolvePath } from '@ginjou/svelte'
const resolvePath = useResolvePath()
const postsHref = $derived(resolvePath({
to: '/posts',
query: {
page: 2,
},
}))
const showHref = $derived(resolvePath({
meta: {
name: 'post-show',
params: {
id: 42,
},
},
}))
</script>
<a href={postsHref}>Posts</a>
<a href={showHref}>Open Post</a>
Unlike useGo() and useBack(), this helper throws when no router context is available.
Navigate by Resource Intent
useNavigateTo() combines router context with resource context.
Instead of passing a raw path every time, you can navigate by resource action and record id. If you pass plain router params, it falls back to the same low-level navigation flow as useGo().
| Input | Meaning |
|---|---|
RouterGoParams | Delegate directly to low-level router navigation. |
{ action: 'list', resource?, params? } | Build a resource path that does not need an id. |
{ action: 'create', resource?, params? } | Build a resource path that does not need an id. |
{ action: 'show', resource?, id, params? } | Build a resource path that needs an id. |
{ action: 'edit', resource?, id, params? } | Build a resource path that needs an id. |
false | Do nothing. |
When you create the helper with resource, later calls can omit the resource name.
<script setup lang="ts">
import { ResourceAction } from '@ginjou/core'
import { useNavigateTo } from '@ginjou/vue'
const navigateTo = useNavigateTo({
resource: 'posts',
})
function openCreate() {
navigateTo({
action: ResourceAction.Type.Create,
})
}
function openPost(id: number) {
navigateTo({
action: ResourceAction.Type.Show,
id,
})
}
function cancel() {
navigateTo({
action: ResourceAction.Type.List,
params: {
page: 1,
},
})
}
</script>
<script lang="ts">
import { ResourceAction } from '@ginjou/core'
import { useNavigateTo } from '@ginjou/svelte'
const navigateTo = useNavigateTo({
resource: 'posts',
})
function openCreate() {
navigateTo({
action: ResourceAction.Type.Create,
})
}
function openPost(id: number) {
navigateTo({
action: ResourceAction.Type.Show,
id,
})
}
function cancel() {
navigateTo({
action: ResourceAction.Type.List,
params: {
page: 1,
},
})
}
</script>
This helper is useful for redirects and controller-driven flows because it stays aligned with the resource definitions from the resource context.
Blocker
A route blocker pauses navigation and waits for the user to decide. Use it for unsaved forms, recordings, live sessions, and anything else that navigation would interrupt.
blocker is optional, because not every router adapter can hold a navigation. When the current router has none, useRouteBlocker() does nothing.
| Prop | Type | Default | What it does |
|---|---|---|---|
shouldBlock | boolean | (input) => boolean | Required | Decides whether to hold a navigation. Return true to hold it. |
enabled | boolean | true | Whether this blocker takes part in navigations at all. |
The result gives you a state and two actions.
| State | Meaning |
|---|---|
unblocked | No navigation is waiting on this blocker. |
blocked | This blocker is being asked to decide. |
proceeding | This blocker has allowed the navigation. |
| Method | What it does | State after calling it |
|---|---|---|
proceed() | Allow the waiting navigation. Works only while blocked. | proceeding, then unblocked once the navigation ends. |
reset() | Cancel the waiting navigation. Works only while blocked. | unblocked |
Show your confirmation while state is blocked, then call proceed() or reset():
<script setup lang="ts">
import { RouteBlocker } from '@ginjou/core'
import { useRouteBlocker } from '@ginjou/vue'
import { computed, ref } from 'vue'
const isDirty = ref(false)
const { state, proceed, reset } = useRouteBlocker({
shouldBlock: ({ currentLocation, nextLocation }) =>
isDirty.value && nextLocation?.path !== currentLocation.path,
})
const isAsking = computed(() => state.value === RouteBlocker.State.Blocked)
</script>
<template>
<dialog :open="isAsking">
<p>You have unsaved changes.</p>
<button type="button" @click="reset()">
Stay
</button>
<button type="button" @click="proceed()">
Leave
</button>
</dialog>
</template>
<script lang="ts">
import { RouteBlocker } from '@ginjou/core'
import { useRouteBlocker } from '@ginjou/svelte'
let isDirty = $state(false)
const blocker = useRouteBlocker({
shouldBlock: ({ currentLocation, nextLocation }) =>
isDirty && nextLocation?.path !== currentLocation.path,
})
const isAsking = $derived(blocker.state === RouteBlocker.State.Blocked)
</script>
<dialog open={isAsking}>
<p>You have unsaved changes.</p>
<button type="button" onclick={blocker.reset}>
Stay
</button>
<button type="button" onclick={blocker.proceed}>
Leave
</button>
</dialog>
Several blockers can block the same navigation, such as a form inside a page that also blocks. They are asked one at a time, in the order they registered, so each page only answers for itself.
shouldBlock returns true, the browser shows its own confirmation. Your blocker state does not change, because the browser decides.Official Adapter
Ginjou ships two official router adapters. Each page covers what to wire up, and how that router behaves with the helpers above.