@route-forge/vue
| Install | |
|---|---|
npm install @route-forge/vue |
|
| Latest Version: | 3.1.0 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Sep 17, 2026 |
| Links: | GitHub · npm |
@route-forge/vue
English | 中文
The Vue 3 integration for Route Forge: a plugin (createRouteForgePlugin), three composables (useForge / useForgeApi / useForgeRoute), and two components (ForgeRoute / ForgeLink) for calling APIs by route name and generating reactive links inside components, with loading and error states managed for you.
All core capabilities (tiered lazy loading, isolated cache, interceptors, request cancellation, type safety) come from @route-forge/core; this package only adds Vue reactivity:
levelLoadedis aRef<boolean>,pending/errorareRefs, and URL generation returns aComputedRef<string>.
Installation
pnpm add @route-forge/vue @route-forge/core
Requires Vue 3.3+ (Vue 2 is not supported).
Quick start
// main.ts
import { createApp } from 'vue'
import { createRouteForgePlugin } from '@route-forge/vue'
import App from './App.vue'
const plugin = createRouteForgePlugin({
endpoint: '/_forge/routes',
})
// plugin.interceptors is exposed synchronously — register one or more interceptors
// here without awaiting ready() (they only affect later api() calls).
plugin.interceptors.request.use((config) => { /* … */ return config })
plugin.interceptors.response.use((resp) => resp.data, (err) => Promise.reject(err))
const app = createApp(App)
app.use(plugin)
// Option A (recommended): mount the app after ready() (summary + eager levels fully loaded);
// sync methods like route()/hasRoute() are then safe immediately
plugin.ready()
.then(() => app.mount('#app'))
.catch((err) => {
// Always handle this: the summary endpoint may be unreachable
// (network error / non-2xx / timeout) — avoid a silent blank page
console.error('[route-forge] init failed', err)
})
// Option B (equally supported): mount immediately and gate inside the component tree —
// <ForgeReady> holds route-dependent content until ready() resolves, same determinism:
// app.mount('#app')
// and wrap route content in App.vue (see ForgeReady below)
Plugin options are exactly
createRouteForge(options)(endpoint/summary/levels/eager/adapter/cache/interceptors/timeout/baseURL); full options table in the core README.optionsitself is optional — when the summary is embedded in the page via@forgeSummary(window.__ROUTE_FORGE__), install with a barecreateRouteForgePlugin().Reuse mode: already hold a forge instance outside Vue (SSR entry, singleton module)? Pass it directly —
createRouteForgePlugin(instance)reuses it and ignores options, symmetric with React's<RouteForgeProvider forge?>.
Option B — direct mount with a deterministic first frame: wrap route-dependent content in the ForgeReady gate component — it renders the fallback slot until ready, then the default slot; equivalent determinism to plugin.ready().then(mount):
<ForgeReady>
<template #fallback><Splash /></template>
<RouterView />
</ForgeReady>
useForge — the core composable
Without level it returns the full RouteForge instance; with level it delegates to forge.use(level, prefix?) (triggering the level load) and returns a VueBoundForge:
import { useForge } from '@route-forge/vue'
// Unbound — full RouteForge instance
const forge = useForge()
forge.api('admin', 'users.show', { user: 1 }) // call with level + route name
forge.ready().then(f => f.use('admin')) // bind a level once ready
// Bound to a level — callable directly, load triggered automatically
const users = useForge('admin')
users.level // → 'admin'
users.levelLoaded // Ref<boolean>, flips to true once loaded
users('users.show', { user: 1 }) // callable (= users.api() shorthand)
users.api('users.show', { user: 1 }) // same thing
users.route('users.show', { user: 1 }) // build a URL
users.url('users.show', { user: 1 }) // semantic alias of route()
await users.onLevelLoaded() // wait until the level is loaded
users.useRoutePrefix('users') // returns a NEW BoundForge with the new prefix
// Bound level + prefix — route names joined automatically (ambiguity resolved smartly)
const userApi = useForge('admin', 'users')
userApi.prefix // → 'users'
userApi('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)
userApi.route('show', { user: 1 }) // → forge.route('admin', 'users.show', ...)
// Generic methods: in bound form they act on the bound level (no arguments)
users.load() // load the bound level
users.isLoaded() // check the bound level's cache
users.invalidate() // invalidate the bound level's cache
// Global methods: isLoading() / onLoadingChange() / hasRoute(name) / getRoutes() / getLevels()
Note: the unbound full instance may throw the guard error (
RF_FE_010) fromroute()/hasRoute()before auto-discovery completes. Preferawait forge.ready(), or useuseForgeRoutefor links.
Level binding is static:
level(andprefix) are fixed at call time and cannot be switched later — create anotheruseForgecall for another level (the overhead is acceptable).
useForgeApi — event-style calls with loading/error state
For imperative scenarios like click handlers: it never throws — errors are written to the error state and returned as { data: undefined, error }:
import { useForgeApi } from '@route-forge/vue'
// Three binding forms (same level semantics as useForge)
const api = useForgeApi() // unbound: call(level, name, params)
const admin = useForgeApi('admin') // bound: call(name, params)
const users = useForgeApi('admin', 'users') // bound + prefix: call(suffix, params)
const { data, error } = await admin.call('users.show', { user: 1 })
pending:Ref<boolean>— reference-counted; concurrentcalls keep ittrueuntil all settleerror:Ref<unknown>— the latest failure (reset tonullon success)
useForgeRoute — reactive links in templates
Returns a ComputedRef<string> (auto-unwrapped in templates, no .value needed). It handles the loading state internally: returns '' until the level loads (the render never crashes), then recomputes automatically when the level loads or dependencies change — you never need to look at levelLoaded:
<template>
<!-- 1. Static URL -->
<a :href="https://github.com/route-forge/route-forge/blob/HEAD/login">Login</a>
<!-- 2. Reactive params: URL recomputes when userId changes -->
<a :href="https://github.com/route-forge/route-forge/blob/HEAD/profile">User home</a>
<!-- 3. Reactive route name: recomputes when currentName changes -->
<NuxtLink :to="dynamic">Dynamic entry</NuxtLink>
</template>
<script setup>
import { ref } from 'vue'
import { useForgeRoute } from '@route-forge/vue'
const userId = ref(1)
const currentName = ref('dashboard.show')
// Static level + static route name
const login = useForgeRoute('public', 'login.show')
// params as a getter → tracked reactively; URL follows userId
const profile = useForgeRoute('admin', 'users.show', () => ({ user: userId.value }))
// name as a getter → the route name itself can switch reactively
const dynamic = useForgeRoute('admin', () => currentName.value)
</script>
Contract details:
levelis a static string binding: fixed once at setup and cannot be switched later — create anotheruseForgeRoutecall for another level (same contract asuseForge); thename/paramsgetters stay reactive. A non-stringlevelthrowsTypeErrorat runtime- Render-time errors (unknown route, missing required param…) degrade to
''so rendering never breaks, while a styledconsole.warnprints the full error (with stack) — visible during development, harmless in production
Components — ForgeRoute / ForgeLink
Both components wrap useForgeRoute, so templates don't repeat the "empty string first, update later" handling. While the level is not loaded (or route resolution fails), the loading slot renders (nothing by default); once loaded, the link renders.
ForgeLink — the convenient one: renders <a :href> directly with slot content as the link text:
<script setup>
import { ForgeLink } from '@route-forge/vue'
</script>
<template>
<!-- Renders <a href="https://github.com/route-forge/route-forge/blob/HEAD/login">Login</a> once the level is loaded -->
<ForgeLink level="public" name="login.show">Login</ForgeLink>
<!-- Reactive params (object or getter form) + attrs passthrough (class / target / …) -->
<ForgeLink level="admin" name="users.show" :params="{ user: userId }" class="btn">
View user
</ForgeLink>
<!-- Custom placeholder while loading -->
<ForgeLink level="admin" name="users.show" :params="() => ({ user: userId })">
View user
<template #loading>Preparing link…</template>
</ForgeLink>
</template>
Router integration is zero-dependency and automatic: if vue-router is installed (app.use(router)), ForgeLink renders <RouterLink :to="href"> for in-app navigation; otherwise it renders a native <a>. Nothing to configure. (A RouterLink registered only locally in a parent component is not detected — the global registration from app.use(router) is what's probed.) For those cases — or any custom link component — pass it via the as prop; the injected component receives both href and to props (mirroring @route-forge/react).
ForgeRoute — the flexible one: exposes { href, loaded } through a scoped slot for full control:
<script setup>
import { ForgeRoute } from '@route-forge/vue'
</script>
<template>
<ForgeRoute level="admin" name="users.show" :params="() => ({ user: userId })">
<template #default="{ href }">
<a :href="https://github.com/route-forge/route-forge/blob/HEAD/href">View user</a>
</template>
<template #loading>Preparing link…</template>
</ForgeRoute>
</template>
Shared contract (both components):
levelis a static string binding (same contract asuseForgeRoute; a non-string throwsTypeErrorat runtime);name/paramsaccept both plain values and getter functions and stay reactiveloaded=href !== ''— inside thedefaultslot it is alwaystrue(the slot only renders once loaded); it exists for symmetry with the React render-prop API- Console behavior: while the level is not loaded each instance
console.warns once (a normal transient state — no spam); route resolution failures logconsole.errorevery time (rendering still never breaks) - SSR: the components simply render the
loadingslot (or nothing) until the level cache is populated — preload the level on the server, or let the link appear after client hydration
About the $forge global property (removed in v3.0.0)
Since v3.0.0 the plugin no longer injects app.config.globalProperties.$forge: it exposed only route() (no api / url / hasRoute) and threw on unready route data, breaking the render — a fragile, misuse-prone entry. Generate links in templates with useForgeRoute / ForgeLink / ForgeRoute; for imperative code use useForge() (unbound calls work directly: forge(level, name, params?)).
Smart parameter resolution
api() supports the same smart resolution as core: flattened path parameters, with query / body / headers as fixed keys; when a path parameter name collides with a fixed key, string|number values are detected as path parameters, and the explicit params key always wins:
// Conflict resolution: route /search/{query} — string `query` → path parameter
users.api('search.show', { query: 'keyword' })
// Explicit params: need BOTH a path param and a query string
users.api('search.show', {
params: { query: 'keyword' },
query: { page: 1 },
})
Full rules and the timeout override: core README.
Type safety (optional but recommended)
Once ForgeRouteMap is defined (codegen or module augmentation), level, route names, and params are inferred in useForge / useForgeApi / useForgeRoute:
npx route-forge-codegen --endpoint http://localhost/_forge/routes --out src/types/forge-routes.d.ts
// Typo'd route name / param name → compile error; correct call → autocompletion
const users = useForge('admin', 'users')
await users('show', { user: 1 }) // ✅ params checked at compile time
See the core README "Type safety" section.
Differences vs core / react
| Capability | @route-forge/core | @route-forge/vue | @route-forge/react |
|---|---|---|---|
levelLoaded |
Promise<void> |
Ref<boolean> |
boolean |
useForgeApi pending / error |
— (use LoadingTracker) |
Ref<boolean> / Ref<unknown> |
boolean / unknown |
| URL generation returns | string (sync; throws when unready) |
ComputedRef<string> ('' until ready) |
string ('' until ready) |
useForgeRoute params |
— | getter function | plain object (content-compared deps) |
| Binding signature | forge.use(level, prefix?) |
useForge(level?, prefix?) |
useForge(level?, prefix?) |
FAQ
$forge.route() doesn't exist in a template?
$forge was removed in v3.0.0. Use useForgeRoute instead (handles the loading state and degrades to ''), or the composable form useForge('admin').route(...).
useForge() says "must be used inside an app with createRouteForgePlugin() installed"?
A composable ran before the plugin was installed, or in a component tree without it; confirm app.use(plugin) happened first.
Need to re-fetch the route table after navigating?
Call forge.invalidate() (or users.invalidate() in bound form); the next api() / load() refetches.
Documentation
- Repository: https://github.com/route-forge/route-forge
- Core package: @route-forge/core
- Design notes: https://github.com/route-forge/route-forge/blob/main/.docs/DESIGN.md
- Specification: https://github.com/route-forge/route-forge/blob/main/.docs/SPEC.md
License
MIT
Related Packages
Foundation package providing base components, utilities, and contracts for all L...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 3.1.0 | ^3.3.0 | MIT | |
| 3.0.0 | ^3.3.0 | MIT | |
| 2.2.1 | ^3.3.0 | MIT | |
| 2.2.0 | ^3.3.0 | MIT | |
| 2.1.0 | ^3.3.0 | MIT | |
| 2.0.0 | ^3.3.0 | MIT | |
| 1.4.0 | ^3.3.0 | MIT | |
| 1.3.0 | ^3.3.0 | MIT | |
| 1.2.2 | ^3.3.0 | MIT | |
| 1.2.1 | ^3.3.0 | MIT | |
| 1.2.0 | ^3.3.0 | MIT | |
| 1.1.1 | ^3.3.0 | MIT | |
| 1.1.0 | ^3.3.0 | MIT | |
| 1.0.3 | ^3.3.0 | MIT | |
| 1.0.2 | ^3.3.0 | MIT | |
| 1.0.0 | ^3.3.0 | MIT | |
| 0.3.0 | ^3.3.0 | MIT | |
| 0.2.0 | ^3.3.0 | MIT | |
| 0.1.1 | ^3.3.0 | MIT | |
| 0.1.0 | ^3.3.0 | MIT |