Guides

Router

Explain the router contract, path resolution, and route-aware navigation helpers.

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

MethodWhat it does
goNavigate to the next location.
backMove one step backward in router history.
resolveConvert route params into a final path string or href.
getLocationReturn the current location snapshot.
onChangeLocationSubscribe to location changes and return a cleanup function.
blockerOptionally register a callback that can hold a navigation. See Blocker.

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
}
PropertyMeaning
toThe target path when you navigate by raw path.
typeChoose push or replace.
queryQuery values for the next location.
hashHash value for the next location.
keepHashReuse the current hash when hash is not passed.
keepQueryMerge the current query into the next query before applying query.
metaAdapter-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>

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>

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
}
PropertyMeaning
pathThe current pathname.
paramsDecoded params extracted from the current path.
queryThe current query object.
hashThe current hash value.
metaAdapter-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>

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>

Unlike useGo() and useBack(), this helper throws when no router context is available.

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().

InputMeaning
RouterGoParamsDelegate 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.
falseDo 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>

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.

PropTypeDefaultWhat it does
shouldBlockboolean | (input) => booleanRequiredDecides whether to hold a navigation. Return true to hold it.
enabledbooleantrueWhether this blocker takes part in navigations at all.

The result gives you a state and two actions.

StateMeaning
unblockedNo navigation is waiting on this blocker.
blockedThis blocker is being asked to decide.
proceedingThis blocker has allowed the navigation.
MethodWhat it doesState 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>

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.

Closing or reloading the tab is handled for you. While any enabled 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.

Vue Router

@ginjou/with-vue-router connects Vue Router to Ginjou's router contract.

Svelte SPA Router

@ginjou/with-svelte-spa-router connects svelte-spa-router to Ginjou's router contract.
Copyright © 2026