mikser-io-sdk-vue
Wire a Vue 3 app to a mikser-io content backend in ~10 lines. Content stays as .md and .yml files on disk — diffable, grep-able, copy-anywhere. The composables below give you live updates over SSE, typed access to layout-shaped front-matter, multilingual URL resolution, transcoded asset URLs, and semantic search.
| What you get | Reads as |
|---|---|
| Live content | const { document } = useDocument(id) — re-renders as the file changes |
| Current document | useCurrentDocument() — the current-route document, one shared subscription (provide once at the root) |
| Live lists | const { documents } = useDocuments({ filter, sort, fields }) |
| Multilingual URLs | href('/about') → /en/about or /fr/a-propos per locale |
| Content by reference | meta('/menu').products — read a known document's fields by its logical $ref, no extra query |
| Hreflang + switchers | useAlternates({ route }) |
| Transcoded asset URLs | url(clip.meta.url) joins a served path from the catalog → <cms>/... |
| Semantic search | useSimilar(store, query) with built-in debounce + stale-discard |
| Live routes | useMikserRoutes(router, { mapRoute }) — augments your router, doesn't replace it |
| Build-time routes | generateMikserRoutes() for SSG manifests |
Augment, don't own. Your app stays yours. Mikser slots in alongside your own router, your own views, your own auth — nothing about the SDK insists on running the whole app.
One mental model across every rendering shape — pure SPA, hybrid SSG with a live editor, mikser-rendered HTML with Vue islands. Same composables, different mount. See examples/ for the three patterns side-by-side.
Typed at the seam. Pair with mikser-io-schemas to author Zod schemas alongside your content; useDocument<{ meta: MetaByLayout<'article'> }>(id) then carries the front-matter shape straight into your templates.
Built on mikser-io-sdk-api — same primitives (live(), list(), entities endpoints, the live-updates contract), wrapped as idiomatic Vue 3.
Zero own dependencies. Vue 3 + Vue Router 4 (optional) + mikser-io-sdk-api 3.x as peer deps.
Install
npm install mikser-io-sdk-vue mikser-io-sdk-api vue vue-router
vue-router is an optional peer — install only if you use any of the router helpers.
Quick start
The app owns the router. Mikser slots catalog-driven routes in alongside your hand-coded ones via useMikserRoutes. The mapRoute callback dispatches each document to the view that matches its meta.component — different content types ship through different views (an article isn't a product isn't a landing page). Dispatch is on meta.component, not meta.layout; layout stays reserved for mikser's SSG render pipeline so the two never collide.
// main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createClient } from 'mikser-io-sdk-api'
import { createMikserPlugin, useMikserRoutes } from 'mikser-io-sdk-vue'
import App from './App.vue'
// One client, one endpoint. `data.catalog` points at the static snapshot
// the data plugin writes (out/data/sitemap.json) — fast first paint
// from a CDN-cacheable file, then SSE deltas keep it current. No second
// API endpoint to configure.
const documents = createClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
.entities('public', { data: { catalog: 'sitemap', entities: 'page' } })
// One Vue view per content component. Each lazy-imports so it ships in
// its own chunk — articles ≠ products ≠ landing pages, code-wise.
const views = {
article: () => import('./views/ArticleView.vue'),
product: () => import('./views/ProductView.vue'),
landing: () => import('./views/LandingView.vue'),
page: () => import('./views/PageView.vue'), // fallback
}
// You construct the router. Hand-coded routes are first-class; mikser
// joins them later via useMikserRoutes.
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/articles', name: 'articles', component: () => import('./views/ArticleIndex.vue') },
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('./views/NotFound.vue') },
],
})
// Plug mikser into the same router. seeded resolves when the initial
// snapshot (or list fallback) lands. Await it before mounting so
// first-paint navigation hits a registered route instead of falling
// through to NotFound.
const { seeded } = useMikserRoutes(router, {
mapRoute: document => ({
path: document.meta.route,
name: document.id,
component: views[document.meta.component] ?? views.page, // dispatch
props: route => ({ entityId: document.id, params: route.params }),
meta: { component: document.meta?.component, title: document.meta?.title },
}),
})
await seeded
createApp(App)
.use(createMikserPlugin({ client: documents }))
.use(router)
.mount('#app')
The out/data/sitemap.json snapshot is produced by the data plugin's catalog config on the mikser side. The block fits in ~10 lines:
// mikser-content/mikser.config.js (server side)
{
plugins: ['documents', 'front-matter', 'plugin-schemas', 'data', 'api'],
data: {
catalog: {
// out/data/sitemap.json — every published, component-having
// document, projected to just the routing fields.
sitemap: {
query: e =>
e.type === 'document' &&
e.meta?.published &&
e.meta?.component,
pick: ['id', 'destination', 'meta.component', 'meta.route', 'meta.title'],
},
},
entities: {
// out/data/<entity.name>.page.json — one file per published
// document, with full content. useDocument(id) reads from
// these on first paint; live updates still flow over SSE.
page: {
query: e => e.type === 'document' && e.meta?.published,
pick: ['id', 'meta', 'content'],
},
},
},
api: {
endpoints: {
public: {
query: e => e.type === 'document' && e.meta?.published,
operations: ['list', 'subscribe'],
cache: true,
},
},
},
}
The data plugin runs at finalize, writes one file per catalog entry under out/data/, served as a static asset by mikser's built-in handler. The pick projection is enforced server-side so the snapshot stays small. query lines up 1:1 with the live() filter the SDK opens after first paint — initial state matches what SSE will send.
See mikser-io-sdk-api → data.catalog for the client side, and examples/mikser-content/mikser.config.js for the full config in context.
The pattern is augment, don't own. You write the app you'd write anyway — your own routes, your own router setup, your own catch-all. Mikser slots its routes in alongside yours and keeps them current as content changes.
Two small things to add when you reproduce this pattern in your own app:
1.
build.target: 'es2022'invite.config.js— top-levelawaitis ES2022 syntax. Vite 5's default'modules'target is below the TLA cutoff andvite builderrors out otherwise. Every evergreen browser (Chrome 89+, Firefox 89+, Safari 15+, Edge 89+) runs es2022 natively. Alternatively, wrap the bootstrap in anasync function main() { … } main()IIFE — no build-target change needed.2. A loading state in
index.html—await seededblocks the mount for the ~100-500ms initial catalog round trip; without a shell the user sees a blank screen. Vue's.mount()replaces#app's inner content, so this disappears the moment routes are seeded:<body> <div id="app"><div class="loading">Loading…</div></div> <script type="module" src="/src/main.js"></script> </body>Both example apps ship with both pieces — see
examples/pure-spaandexamples/hybrid-ssg.
What entityId is
entityId is the mikser entity id — every document, file, and asset in the catalog has one. For documents it's the source file path under the working folder, e.g. /documents/en/articles/welcome.md. It's stable across content edits (only a rename changes it) and globally unique within a mikser instance.
The flow through the Quick Start above:
mapRoute receives a document → its document.id is '/documents/en/articles/welcome.md'
passes document.id as prop → ArticleView gets <ArticleView :entity-id="…welcome.md" />
useDocument(entityId) → opens a live subscription for { id: '…welcome.md' }
SDK filters by id → server returns just that one document
editor edits the file → SSE event flows back → component re-renders
The entityId prop name follows mikser's vocabulary — documents, files, assets are all entities, and entityId is what the catalog calls their identifier. The SDK doesn't reserve the name; you can call the prop anything, but staying with entityId makes a Vue codebase recognisable to anyone who already knows mikser's terms.
Documents declare their component in front-matter — the router uses it to pick the view. (layout stays separate, reserved for mikser's SSG render pipeline.)
# documents/en/articles/welcome.md
---
component: article # ← drives the dispatch to ArticleView.vue
title: Welcome
author: Alice Park
date: 2026-05-01
route: /en/articles/welcome
published: true
---
# documents/en/products/desk-lamp.md
---
component: product # ← drives the dispatch to ProductView.vue
title: Desk Lamp
price: 149
image: /assets/desk-lamp.jpg
sku: LMP-001
route: /en/products/desk-lamp
in_stock: true
---
# documents/en/campaigns/spring-sale.md
---
component: landing # ← drives the dispatch to LandingView.vue
title: Spring Sale
hero: /assets/spring-hero.jpg
cta: { label: 'Shop now', href: '/products' }
route: /en/campaigns/spring-sale
---
Three documents, three different components → three structurally different views. None of them share template code; they all share the same data primitive (useDocument).
ArticleView.vue — article shape. Headline, byline, date, body.
<!-- views/ArticleView.vue -->
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps({ entityId: String })
const { document: article, loading } = useDocument(() => props.entityId)
</script>
<template>
<article v-if="article" class="article">
<header>
<h1>{{ article.meta?.title }}</h1>
<p class="byline">
By <strong>{{ article.meta?.author }}</strong>
· <time>{{ article.meta?.date }}</time>
</p>
</header>
<div class="article-body" v-html="article.content" />
<footer>
<router-link to="/articles">← All articles</router-link>
</footer>
</article>
<p v-else-if="loading">Loading…</p>
</template>
ProductView.vue — product shape. Image gallery, price, stock state, add-to-cart, description. Nothing in common with an article visually.
<!-- views/ProductView.vue -->
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps({ entityId: String })
const { document: product } = useDocument(() => props.entityId)
function addToCart() { /* hand off to the cart store */ }
</script>
<template>
<section v-if="product" class="product">
<figure class="product-image">
<img :src="product.meta?.image" :alt="product.meta?.title" />
</figure>
<div class="product-info">
<h1>{{ product.meta?.title }}</h1>
<p class="sku">SKU {{ product.meta?.sku }}</p>
<p class="price">€{{ product.meta?.price }}</p>
<p v-if="product.meta?.in_stock" class="stock in">In stock</p>
<p v-else class="stock out">Out of stock</p>
<button :disabled="!product.meta?.in_stock" @click="addToCart">
Add to cart
</button>
<div class="product-description" v-html="product.content" />
</div>
</section>
</template>
LandingView.vue — landing-page shape. Full-bleed hero, CTA, body sections. Again, nothing in common with the other two structurally.
<!-- views/LandingView.vue -->
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps({ entityId: String })
const { document: page } = useDocument(() => props.entityId)
</script>
<template>
<div v-if="page" class="landing">
<section class="hero" :style="{ backgroundImage: `url(${page.meta?.hero})` }">
<h1>{{ page.meta?.title }}</h1>
<a v-if="page.meta?.cta" :href="page.meta.cta.href" class="cta-button">
{{ page.meta.cta.label }}
</a>
</section>
<section class="content" v-html="page.content" />
</div>
</template>
ArticleIndex.vue — a collection listing using useDocuments. Different shape entirely (a list, not a single document); same live-update contract underneath.
<!-- views/ArticleIndex.vue -->
<script setup>
import { useDocuments } from 'mikser-io-sdk-vue'
const { documents: articles } = useDocuments({
filter: { 'meta.component': 'article', 'meta.published': true },
sort: { 'meta.date': -1 },
fields: ['id', 'meta.title', 'meta.date', 'meta.author', 'meta.summary', 'meta.route'],
limit: 20,
})
</script>
<template>
<main>
<h1>Articles</h1>
<ul class="article-list">
<li v-for="a in articles" :key="a.id">
<router-link :to="a.meta.route">
<h2>{{ a.meta.title }}</h2>
<p class="byline">{{ a.meta.author }} · {{ a.meta.date }}</p>
<p>{{ a.meta.summary }}</p>
</router-link>
</li>
</ul>
</main>
</template>
The five compose under live updates:
- Editor publishes a new article in Decap →
ArticleIndexreceives thecreateevent and the new card appears at the top of the list, no refresh. - Click it → router dispatches via
meta.component: 'article'→ArticleViewmounts → itsuseDocumentsubscription opens → editor edits the body → the article re-renders in place. - Browse to
/en/products/desk-lamp→ router dispatches viameta.component: 'product'→ProductViewmounts with image, price, CTA — visibly nothing like an article. - Editor toggles
in_stock: falsein the front-matter →ProductViewre-renders with the button disabled and the stock label flipped. Same composable. Different shape. Same live update.
useDocument is the data primitive — every content view in the app uses it. The view's job is just to render a particular shape on top of that data. The router decides which shape via meta.component. The dispatch is what makes this scale to dozens of content types without growing one giant DocumentPage component.
That's the whole story. Everything below is detail.
Scenarios — picking the right shape for your project
Three common shapes. Each makes a different trade between SEO, build complexity, and how much Vue does. Pick before you start; mixing them mid-project is painful.
Runnable starter projects
Each scenario ships as a complete starter under
examples/— Vite config,package.json, full source tree, its own README explaining how to run it. Clone and modify rather than translate the snippets below into project structure.
Folder What's in it examples/mikser-contentThe shared content server — a standalone mikser project that supplies the catalog to the three Vue apps below. Start it first. examples/pure-spa(scenario A)Vite + Vue + useMikserRoutesagainst your own routerexamples/dynamic-spa(scenario D)Same shape as pure-spabut with a catch-all +useDocumentByRouteexamples/hybrid-ssg(scenario B)Two Vite configs (public + editor), shared route-mapping.jsexamples/islands(scenario C)Multi-entry Vite build, three Vue islands mounting onto mikser-rendered HTML
A) Pure SPA — runtime everything, live everywhere
When: Editor UIs, admin dashboards, internal apps. SEO doesn't matter. You want the fastest dev loop and the lowest build complexity.
How it works: No build-time route enumeration. The app constructs its own router, then wires useMikserRoutes against it to slot catalog routes in alongside the hand-coded ones. Editing a document → SSE event → addRoute / removeRoute → UI updates. No rebuild ever.
// main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createClient } from 'mikser-io-sdk-api'
import { createMikserPlugin, useMikserRoutes } from 'mikser-io-sdk-vue'
import App from './App.vue'
const documents = createClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
.entities('public', { data: { catalog: 'sitemap', entities: 'page' } })
const router = createRouter({
history: createWebHistory(),
routes: [
// Your hand-coded routes
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('./views/NotFound.vue') },
],
})
// seeded resolves when the initial snapshot (or list fallback) lands.
const { seeded } = useMikserRoutes(router, {
mapRoute: document => ({
path: document.meta.route,
name: document.id,
component: () => import('./views/DocumentPage.vue'),
props: route => ({ entityId: document.id, params: route.params }),
meta: { component: document.meta?.component },
}),
})
await seeded
createApp(App)
.use(createMikserPlugin({ client: documents }))
.use(router)
.mount('#app')
Trade-offs: Fastest to set up. Worst for SEO (the public-facing HTML is empty until JS loads). Initial boot pays a listAll() round trip (~100-500ms for typical sites).
Full starter project:
examples/pure-spa— clone,npm install, setVITE_MIKSER_URL,npm run dev.
B) Hybrid — SSG for public, SPA-with-live for editor
When: Marketing sites, blogs, documentation, any content site that needs SEO. The typical agency project.
The idea: Two builds from the same content. The public deploy is pre-rendered HTML (one file per route, optimised for crawlers + CDN). The editor / admin app is the SPA from scenario A, talking to the same mikser server. Both share the same mapRoute function, so they agree on what a route is.
Build script — runs in CI before the production build:
// build/generate-routes.mjs
import { writeFile } from 'node:fs/promises'
import { createClient } from 'mikser-io-sdk-api'
import { generateMikserRoutes } from 'mikser-io-sdk-vue'
import { mapRoute } from '../src/route-mapping.js' // shared
const documents = createClient({ baseUrl: process.env.MIKSER_URL }).entities('public')
const routes = await generateMikserRoutes({ client: documents, mapRoute })
await writeFile('./src/generated/routes.json',
JSON.stringify(routes.map(r => ({ ...r, component: undefined })), null, 2))
// ^ strip the component (it's a function — not JSON-serializable). The
// runtime router rehydrates it via mapRoute at boot.
Production router — reads the manifest, no list() call needed:
// src/router.js (production build)
import { createRouter, createWebHistory } from 'vue-router'
import generatedRoutes from './generated/routes.json'
import DocumentPage from './views/DocumentPage.vue'
export const router = createRouter({
history: createWebHistory(),
routes: [
...staticRoutes,
...generatedRoutes.map(r => ({ ...r, component: DocumentPage })),
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
],
})
Then run Vite SSG or vite-plugin-ssr to pre-render each route's HTML. Each route's page calls useDocument() during SSR to fetch the content — produces a fully-rendered HTML file.
Editor app — separate entry point, uses scenario A (useMikserRoutes + live composables). Mounted under /admin/* or on a different domain. Stays live always.
project/
src/
public/main.js ← SSG entry, uses generated routes
editor/main.js ← SPA entry, uses createRouter + useMikserRoutes (live)
build/
generate-routes.mjs ← run before vite build
Trade-offs: Two entry points, two build steps, slightly more wiring. In exchange: SEO-correct, CDN-friendly public deploy + live editor preview from the same content source.
Full starter project:
examples/hybrid-ssg— the load-bearing file issrc/route-mapping.js, shared between three consumers (build script, public router, editor router).
C) Mikser-rendered HTML + Vue islands
When: Content-heavy sites where most pages are pure content (mikser renders them perfectly) but a few features need interactivity (search box, contact form, filters, live counts).
The idea: Mikser is responsible for the HTML. Vue is just an enhancement layer that mounts onto specific DOM nodes the server-rendered HTML emits. No vue-router involved — the URLs are real URLs served as static files.
Public site: mikser build produces out/. Deploy out/ as static. The HTML includes a mount point for the Vue island:
<!-- documents/en/search.md → rendered via layouts/page.html.hbs -->
<article>
<h1>{{meta.title}}</h1>
<div id="search-island" data-endpoint="public"></div>
</article>
Vue island bundle — separate Vite build, mounted on demand:
// src/islands/search.js
import { createApp } from 'vue'
import { createClient } from 'mikser-io-sdk-api'
import { createMikserPlugin } from 'mikser-io-sdk-vue'
import SearchBox from './SearchBox.vue'
const el = document.getElementById('search-island')
if (el) {
const documents = createClient({ baseUrl: '/' }) // same-origin
.entities(el.dataset.endpoint)
createApp(SearchBox)
.use(createMikserPlugin({ client: documents }))
.mount(el)
}
SearchBox.vue — uses useDocuments() to query mikser for results:
<script setup>
import { ref, computed } from 'vue'
import { useDocuments } from 'mikser-io-sdk-vue'
const q = ref('')
const query = computed(() => ({
filter: q.value ? { 'meta.title': { $regex: q.value } } : { id: '__none__' },
fields: ['id', 'meta.title'],
limit: 10,
}))
const { documents } = useDocuments(query)
</script>
<template>
<input v-model="q" placeholder="Search…" />
<ul><li v-for="document in documents" :key="document.id">{{ document.meta.title }}</li></ul>
</template>
Trade-offs: Best performance (static HTML + small Vue bundle, lazy-loaded). Simplest deployment (just files). But Vue doesn't own routing — the URL structure is mikser's responsibility.
Full starter project:
examples/islands— three islands (search, booking, cart-counter) and a simulated mikser-rendered HTML page showing where they mount.
D) Dynamic routes — for catalogs too big to enumerate
When: A content catalog past the ~5k–10k route mark where loading every route into a snapshot at boot stops making sense — large blogs, e-commerce catalogs, knowledge bases, document archives.
The idea: Stop enumerating routes. Install one catch-all pattern in the router; resolve the document at navigation time via useDocumentByRoute(path). The api plugin's per-query disk cache turns each unique route into an on-demand static file: the first user hits mikser, subsequent users get the cached response served by the reverse proxy. Effectively per-route ISR with no extra config.
// main.js — note no data.catalog, no useMikserRoutes
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createClient } from 'mikser-io-sdk-api'
import { createMikserPlugin } from 'mikser-io-sdk-vue'
import App from './App.vue'
const documents = createClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
.entities('public')
const router = createRouter({
history: createWebHistory(),
routes: [
// Hand-coded routes first
{ path: '/', name: 'home', component: () => import('./views/Home.vue') },
{ path: '/search', name: 'search', component: () => import('./views/Search.vue') },
// Single catch-all for everything content-backed
{ path: '/:pathMatch(.*)*', name: 'doc', component: () => import('./views/DocumentResolver.vue') },
],
})
createApp(App)
.use(createMikserPlugin({ client: documents }))
.use(router)
.mount('#app')
<!-- views/DocumentResolver.vue -->
<script setup>
import { useRoute } from 'vue-router'
import { useDocumentByRoute } from 'mikser-io-sdk-vue'
import ArticleView from './ArticleView.vue'
import ProductView from './ProductView.vue'
import PageView from './PageView.vue'
import NotFound from './NotFound.vue'
const route = useRoute()
const { document, loading } = useDocumentByRoute(() => route.path)
const views = { article: ArticleView, product: ProductView, page: PageView }
</script>
<template>
<p v-if="loading">Loading…</p>
<component v-else-if="document" :is="views[document.meta?.component] ?? PageView" :entity-id="document.id" />
<NotFound v-else />
</template>
How the caching works. useDocumentByRoute issues a request like GET /api/public/entities?meta.route=/en/about&meta.published=true&limit=1&cache=4f3a2c1d8e9b6f7a. The SDK appends the cache=<sha256-prefix> param automatically. With cache: true on the public endpoint mikser writes that response to out/api/public/entities/4f3a2c1d8e9b6f7a.json (named by the same hash the SDK sent). The standard nginx failover config — try_files /api/public/entities/$arg_cache.json @proxy — serves the file directly on subsequent requests via the client-provided hint, no Lua needed. See mikser-io's caching docs for the full config across nginx / Caddy / Cloudflare / Apache.
So the architecture is:
- First visitor to a route: SDK → mikser → response served + written to disk
- Every subsequent visitor: SDK → proxy serves the cached file (mikser idle)
- Catalog change: entire cache directory cleared, re-warms on demand
Trade-offs: First paint on a cold route pays one API roundtrip — slower than the snapshot model's pre-loaded routes, faster than a registered SPA's full-tree boot once the cache is warm. Doesn't scale down well to small catalogs (you're paying the catch-all-resolver tax for routes you could have enumerated for free) but scales up beautifully — works the same at 10k routes as at 10M.
When to pick D over A: roughly when data.catalog.sitemap projection would emit more than ~1–2 MB. Past that the snapshot is dragging first paint down more than the resolver does.
Full starter project:
examples/dynamic-spa— same shape aspure-spabut with the catch-all pattern wired up. Compare them side-by-side to see the diff.
Picking between them
| Question | A (SPA) | B (Hybrid SSG) | C (Islands) | D (Dynamic SPA) |
|---|---|---|---|---|
| Do you need SEO? | No | Yes | Yes | No |
| Is most of the page interactive? | Yes | Maybe | No | Yes |
| Is content mostly static? | No | Yes | Yes | No |
| Editor + admin in same app? | Yes | Editor is the SPA half | Separate admin app | Yes |
| Build complexity tolerance | Low | Medium | Low | Low |
| Mikser plugins (post-pdf, post-mjml) used? | No | Maybe | Yes | No |
| Catalog size | < 5k routes | any | any | > 5k routes |
Rule of thumb for an agency client site: start with C (islands) for the public site if the content is mostly static, B (hybrid SSG) if there's significant interactivity, A (pure SPA) only for the admin app. Pick D when A would otherwise be your choice but the catalog is past the snapshot ceiling. A/D and B/C often coexist in the same project — the admin is always SPA-shaped; the public face is the project-by-project decision.
When to use which composable
| Composable | Best for | Avoid when |
|---|---|---|
client.list() directly |
Build-time, SSR (no live updates needed) | Component that needs to react to changes |
useDocument() / useDocuments() |
Components in any scenario | Plain Node scripts (use the SDK directly) |
useDocumentByRoute() |
Scenario D catch-all view — resolve the current path to a document | Scenarios A/B (you have the entity id; use useDocument) |
live() underneath both |
Always — they wrap it | — |
useMikserRoutes |
Scenarios A or B (editor app) — your router, mikser slots in | Scenario C (no router) |
generateMikserRoutes |
Scenario B (build step) | Scenarios A or C |
useHref + useAsset |
Any scenario with Vue components | Mikser-rendered HTML (use the render-href plugin server-side instead) |
useDocument / useDocuments are safe to use in SSR — when there's no DOM, they fetch the initial snapshot and the live() subscription is a no-op (the response stream closes when the server finishes rendering). On the client, hydration re-subscribes and live updates resume.
Surface
Each export does one job. Mikser augments your app — you own the router; the SDK helpers slot in alongside.
| Export | What it does |
|---|---|
createMikserPlugin |
Vue plugin — provides the entities client app-wide |
useMikserClient |
Injection accessor for the raw client (rare; the other composables use it) |
useDocument |
Reactive single-document composable with live updates |
useDocuments |
Reactive list composable with live updates |
provideCurrentDocument / useCurrentDocument |
One shared current-route document for the subtree — provide once, read anywhere. References resolve by default ($ wildcard); expand: [] opts out |
useMikserRoutes |
Live diff (addRoute / removeRoute) against your existing router. Returns { dispose, seeded }. |
generateMikserRoutes |
Build-time helper — outputs a routes array for static-build pipelines |
provideHrefIndex / useHref |
Multilingual URL abstraction — resolve /about → /en/about per locale |
useAlternates |
Alternate-language URLs for the current route — language switchers + SEO hreflang |
useAsset |
Format-neutral asset helpers — url(ref) joins a deployed served path to the client base; asset(ref) looks up a managed entity (needs provideAssetIndex) |
createReactiveCache |
Load-once, expand-capable reactive content cache — readable from non-component code (Pinia stores, plain modules), not just templates |
createMikserVectorPlugin / useMikserVectorClient |
Bridges mikser-io-sdk-vector into Vue's provide/inject |
useSimilar |
Live semantic search with debounce + stale-result discard |
Composables — document data
useDocument(id, options?)
const { document, loading, error, refresh } = useDocument(() => props.entityId)
idcan be a string, a Ref, or a getter. When it changes, the subscription re-subscribes for the new id.documentis null while loading or when the document doesn't exist.- Live-updates via
client.live({ id })under the hood — when the document changes server-side, the ref updates without manual refetch. - Disposes the subscription on
onUnmounted.
useDocument<T>(...) is generic — pass the entity shape and document is typed accordingly. The recommended source for T is the entities.d.ts emitted by mikser-io-schemas, which generates one XxxMeta alias per layout from Zod schemas in the mikser project:
import type { MetaByLayout } from '../mikser-content/entities'
const { document } = useDocument<{ meta: MetaByLayout<'article'> }>(id)
// document.value.meta.title / .author / .summary are all typed
Without mikser-io-schemas, you can still pass a hand-written interface — useDocument<MyArticle>(id) — the SDK doesn't care where T comes from.
useDocuments(query, options?)
const { documents, loading, error, refresh } = useDocuments({
filter: { type: 'document', 'meta.published': true },
sort: { 'meta.date': -1 },
fields: ['id', 'meta.title', 'meta.date', 'meta.summary'],
limit: 20,
})
querycan be a static object, a Ref, or a getter. Deep-watched — when the filter / sort / fields / limit change, the subscription re-evaluates.documentsis reactive — pushes to<v-for>update in place as the underlying content changes.
The current-route document
Most routes render one document end-to-end — the page <head>, the body copy, the media. provideCurrentDocument loads that document once for the current route and shares it with the whole subtree; every child reads it with useCurrentDocument(). No prop-drilling, no global, one subscription.
// App.vue — wraps every route
import { provideCurrentDocument } from 'mikser-io-sdk-vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const { document, loading } = provideCurrentDocument({
route: () => route.path, // a path string, Ref, or getter — the SDK reads it, never imports your router
expand: [], // ← the one decision that matters; see below
})
useHead(/* derive from document.value?.meta.head */)
// any descendant
const { document } = useCurrentDocument()
Who owns the references
A document carries $-keyed references — a $video, a $hero, a list of $related. Resolving them is expand's job, and the default is the $ wildcard: call provideCurrentDocument with no expand and the document arrives with every reference resolved. That's the right default — a document should come whole.
But the root provide in App.vue is the wrong place to do that resolving. It serves two kinds of consumer, and it's the ancestor of every route:
- the page
<head>and components that read plain fields (meta.title,meta.prices) never touch a reference; - a uniform expand at the root would make the login form resolve the product videos it never renders.
So the root provide opts out — expand: [] — and each view that actually renders references provides its own current-route document. That provide shadows the root for its subtree and takes the default [':
// views/Home.vue — renders the presentation + FAQ videos
import { provideCurrentDocument } from 'mikser-io-sdk-vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const { document } = provideCurrentDocument({ route: () => route.path }) // default expand: ['
The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the expand. [' is the default because resolved is the common case; [] is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
provideCurrentDocument is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
// composables/document.js
import { useRoute } from 'vue-router'
import { provideCurrentDocument } from 'mikser-io-sdk-vue'
import { client } from '../lib/mikser'
// A view owns its document load; references resolve by default ($ wildcard).
// Pass [] to opt out, or a path list to narrow.
export function provideRouteDocument(expand) {
const route = useRoute()
return provideCurrentDocument({ client, route: () => route.path, expand })
}
// App.vue → provideRouteDocument([]) // neutral: head + plain fields
// views/Home.vue (renders refs) → provideRouteDocument() // default ['
One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
useMikserRoutes(router, options)
The common case. Wires a live diff loop against an existing vue-router instance: addRoute for catalog entries that appear, removeRoute for entries that disappear. Returns { dispose, seeded }.
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createClient } from 'mikser-io-sdk-api'
import { createMikserPlugin, useMikserRoutes } from 'mikser-io-sdk-vue'
import App from './App.vue'
// One client. `data.catalog` pulls the narrow snapshot the data plugin
// writes — fast first paint, CDN-cacheable — then live SSE keeps it
// current.
const documents = createClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
.entities('public', { data: { catalog: 'sitemap', entities: 'page' } })
// Your router. Your routes. Your guards.
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/login', name: 'login', component: () => import('./views/Login.vue') },
{ path: '/dashboard', name: 'dashboard', component: () => import('./views/Dashboard.vue') },
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('./views/NotFound.vue') },
],
})
// Plug mikser into the same router. Await seeded before mount.
const { seeded } = useMikserRoutes(router, {
mapRoute: document => ({
path: document.meta.route,
name: document.id, // required — used as diff key
component: () => import('./views/DocumentPage.vue'),
props: { entityId: document.id },
}),
})
await seeded
createApp(App)
.use(createMikserPlugin({ client: documents }))
.use(router)
.mount('#app')
mapRoute must return routes with a name — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones;
addRoute overwrites on duplicate names.
seeded resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
dispose() stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
generateMikserRoutes(options)
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
// build/generate-routes.mjs
import { writeFile } from 'node:fs/promises'
import { createClient } from 'mikser-io-sdk-api'
import { generateMikserRoutes } from 'mikser-io-sdk-vue'
const documents = createClient({ baseUrl: process.env.MIKSER_URL }).entities('public')
const routes = await generateMikserRoutes({
client: documents,
mapRoute: document => ({ path: document.meta.route, name: document.id, /* … */ }),
})
await writeFile('./src/generated/routes.json', JSON.stringify(routes, null, 2))
Same mapRoute function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual href() — provideHrefIndex + useHref
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references —
/about, /contact, /blog/welcome — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs —
/en/about vs /fr/a-propos vs /de/uber-uns — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write /en/about in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
useHref() makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (
/uber-uns in DE, /a-propos in FR, /about in EN) without per-language code paths
- Reactive locale switching — change
locale.value, every :to updates
- Broken-link visibility —
href('/typo') returns /typo unchanged, so QA sees it in the DOM instead of hitting silent undefined failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
Field
Purpose
Example
href
Logical reference — the same across all translations
/about
lang
Which language this translation represents
en, fr, de, bg
route
The deployed URL — what users actually navigate to
/en/about, /fr/a-propos
# documents/en/about.md
---
title: About us
href: /about # logical reference
lang: en # which language
route: /en/about # deployed URL
---
# documents/fr/a-propos.md
---
title: À propos
href: /about # same logical ref — this is the FR translation of "about"
lang: fr
route: /fr/a-propos # localised slug — the URL the browser navigates to
---
# documents/de/uber-uns.md
---
title: Über uns
href: /about # same logical ref
lang: de
route: /de/uber-uns
---
Three documents, one logical /about, three localised slugs. provideHrefIndex builds the lookup { '/about': { en: '/en/about', fr: '/fr/a-propos', de: '/de/uber-uns' } } from the catalog and keeps it live as content changes.
Documents without meta.lang (e.g., single-language pages like /legal/terms) go in a 'default' bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
<!-- App.vue -->
<script setup>
import { provideHrefIndex } from 'mikser-io-sdk-vue'
provideHrefIndex() // uses the injected client
</script>
<template>
<router-view />
</template>
The index is reactive — it stays in sync with the catalog via client.live(). Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n() // your existing i18n state
const { href } = useHref(locale) // reactive — re-resolves when locale changes
</script>
<template>
<nav>
<router-link :to="href('/')">Home</router-link>
<router-link :to="href('/about')">About</router-link>
<router-link :to="href('/contact')">Contact</router-link>
<!-- Explicit override — show this link in French regardless of current locale -->
<a :href="href('/about', 'fr')">Voir en français</a>
</nav>
</template>
Content by reference — meta(ref) / doc(ref)
The href index already loads every published document keyed by its logical meta.href, so it doubles as a content index. useHref() exposes meta(ref) (and doc(ref)) alongside href(ref) — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
const { href, meta } = useHref()
</script>
<template>
<!-- href(ref) → URL ; meta(ref) → that document's content. Both reactive. -->
<ul class="menu">
<li v-for="item in meta('/menu')?.products" :key="item.id">
<router-link :to="href(item.ref)">{{ item.name }}</router-link>
</li>
</ul>
</template>
meta(ref) returns null for an unknown ref (vs. href(ref) which echoes the ref back so broken links stay visible). It reads from the same live subscription as href(), so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with mikser-io-schemas to type the returned meta per layout.
When the user toggles locale.value, every :to binding re-evaluates. No watch, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical href, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as useAlternates() — no manual index traversal needed.
<!-- components/LanguageSwitcher.vue -->
<script setup>
import { useRoute } from 'vue-router'
import { useAlternates } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const route = useRoute()
const { locale, availableLocales } = useI18n()
// Explicit language list — show every locale the app supports, even
// for pages where a translation doesn't exist yet (the SDK falls back
// via href()'s resolution chain). Right shape for a switcher.
const { alternates } = useAlternates({
route: () => route.path,
languages: availableLocales,
})
</script>
<template>
<ul class="lang-switcher">
<li v-for="alt in alternates" :key="alt.lang">
<router-link :to="alt.url">{{ alt.lang.toUpperCase() }}</router-link>
</li>
</ul>
</template>
The alternates ref is reactive on route.path — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on current from the same composable if you want to include it).
Now a visitor on /en/about clicking "FR" lands on /fr/a-propos — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
const { href } = useHref(locale)
const crumbs = [
{ ref: '/', label: 'Home' },
{ ref: '/products', label: 'Products' },
{ ref: '/products/sofa-line', label: 'Sofa Line' },
]
</script>
<template>
<nav class="crumbs">
<router-link v-for="c in crumbs" :key="c.ref" :to="href(c.ref)">
{{ c.label }}
</router-link>
</nav>
</template>
The labels would normally come from i18n too ($t('crumbs.products')). The point is the URLs — c.ref stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip href() for that link — href() is for the static references in your code, not for runtime-resolved item URLs.
<script setup>
import { useDocuments } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
// Query for posts in the current language.
const { documents: posts } = useDocuments(() => ({
filter: { 'meta.collection': 'posts', 'meta.lang': locale.value },
sort: { 'meta.date': -1 },
fields: ['id', 'meta.title', 'meta.route'],
limit: 20,
}))
</script>
<template>
<ul>
<li v-for="post in posts" :key="post.id">
<router-link :to="post.meta.route">{{ post.meta.title }}</router-link>
</li>
</ul>
</template>
Collection items use their own meta.route directly. Use href() for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
href() returns a string — it composes with router.push() and any other place a URL is needed:
import { useRouter } from 'vue-router'
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const router = useRouter()
const { locale } = useI18n()
const { href } = useHref(locale)
async function onSubmit() {
await saveForm()
router.push(href('/thank-you'))
}
Resolution behavior in detail
The fallback chain: requested lang → 'default' bucket → any available language → the input reference unchanged.
Call
Index state
Result
Why
href('/about', 'en')
{ '/about': { en: '/en/about', fr: '/fr/a-propos' } }
/en/about
Exact match
href('/about', 'de')
Same (no DE document exists)
/en/about (or the first available)
Falls back to any available language so the link still works
href('/contact', 'en')
{ '/contact': { default: '/contact' } }
/contact
Single-language documents live in default; matches across any locale request
href('/contact', 'en')
{ '/contact': { default: '/contact', en: '/en/contact' } }
/en/contact
Locale-specific wins over default when present
href('/typo')
Not in index
/typo
Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them.
href('/about') (no lang passed)
—
Uses defaultLangRef from useHref(localeRef)
The composable's default lang
The pass-through behaviour for unresolved refs is deliberate. Silently returning undefined or '/' would hide link rot from sight. Passing the input through means broken links render as broken anchors (<a href="/typo">…</a>) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want <link rel="alternate" hreflang="…"> in the document head for every language version of the current page. The href index is exactly the data structure for that:
<!-- AppHead.vue (use with Vue Meta / Unhead / Vue 3's built-in head support) -->
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useHead } from '@unhead/vue'
import { useAlternates } from 'mikser-io-sdk-vue'
const route = useRoute()
// No `languages` arg — return only translations that actually exist.
// Right shape for hreflang: don't advertise pages we don't have.
const { alternates } = useAlternates({ route: () => route.path })
useHead({
link: computed(() => alternates.value.map(({ lang, url }) => ({
rel: 'alternate',
hreflang: lang,
href: url,
}))),
})
</script>
Same composable, different shape of the answer — explicit languages for switchers (fall back when a translation doesn't exist), no languages for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood provideHrefIndex uses client.live({ 'meta.href': { $exists: true } }) to subscribe to every document with an href field, and recomputes the index whenever the catalog changes. The same SSE stream that powers useDocument / useDocuments keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all
useHref() consumers update on the next render
- Deleting a document → its locale entry drops out of the index;
href() falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
// build/generate-href-index.mjs
import { writeFile } from 'node:fs/promises'
import { createClient } from 'mikser-io-sdk-api'
const documents = createClient({ baseUrl: process.env.MIKSER_URL }).entities('public')
const { items } = await documents.list({
filter: { 'meta.href': { $exists: true } },
fields: ['id', 'meta'],
limit: 10_000,
})
const index = {}
for (const d of items) {
const ref = d.meta?.href, lang = d.meta?.lang ?? 'default'
const url = d.meta?.route ?? d.meta?.destination ?? ref
if (!ref) continue
if (!index[ref]) index[ref] = {}
index[ref][lang] = url
}
await writeFile('./src/generated/href-index.json', JSON.stringify(index, null, 2))
Then ship a tiny replacement for provideHrefIndex that reads the JSON and provides it statically. The useHref() consumer code doesn't change.
Server-side correspondence
The Vue-side href() mirrors what mikser's own render-href plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same meta.href / meta.lang / meta.route fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at /about and a Vue-rendered page that calls href('/about') produce the same URL for the same locale.
Asset references — useAsset
mikser's assets() plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. useAsset() returns { url, asset, index }.
url(ref) — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — meta.url for a served file, meta.presets.<name> for a transcoded derivative — and surfaced into the catalog via expand. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no /assets/<preset>/<source> assembly to keep in sync with the plugin.
watchAssetFallbacks() is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an <img>/<video> that fetched HTML instead of media).
watchUnmatchedRoutes(router) is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty <router-view> with no error. Catalog routes name the canonical href (/web) and live at the localized path (/), so reaching for the href as a path is the common miss; the warning resolves the name to the real route ('/web' … its route is '/'). Pairs with createMikserHistory(), which fixes the deep-load percent-encoding miss.
<!-- components/Clip.vue -->
<script setup>
import { useAsset } from 'mikser-io-sdk-vue'
const { url } = useAsset()
</script>
<template>
<video :src="url(clip.meta.url)"
:poster="url(clip.meta.presets.poster)"></video>
</template>
asset(ref) — managed-entity lookup
Resolves a managed asset entity by id to { url, meta } | null. meta is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs provideAssetIndex() in a parent; without it asset(ref) returns null.
<!-- App.vue -->
<script setup>
import { provideAssetIndex } from 'mikser-io-sdk-vue'
provideAssetIndex()
</script>
const { asset } = useAsset()
const { url, meta } = asset('/assets/hero.jpg') ?? {}
Image-specific rendering (srcset, <img> props) is a consumer concern: read meta where you actually know the asset is an image, and build whatever element you need from it.
provideAssetIndex is only needed for asset(ref) entity lookups. url(...) works without it.
References & inline expansion
Reference fields written as $author: /authors/dick (per ADR-0007) arrive on the wire as bare href strings — document.value.meta.author === '/authors/dick'. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained useDocument — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps(['id'])
const { document: article } = useDocument(() => props.id)
const { document: author } = useDocument(() => article.value?.meta.author)
</script>
<template>
<article v-if="article && author">
<h1>{{ article.meta.title }}</h1>
<p>By {{ author.meta.name }}</p>
</article>
</template>
expand on useDocument / useDocuments — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like author.organization, or arrays of refs like sections.*.image).
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps(['id'])
// Single call returns article + author + author.organization + hero
// in one round-trip. The initial paint shows fully-resolved data.
const { document: article } = useDocument(() => props.id, {
expand: ['author.organization', 'hero'],
})
</script>
<template>
<article v-if="article">
<h1>{{ article.meta.title }}</h1>
<p>
By {{ article.meta.author.meta.name }}
— {{ article.meta.author.meta.organization.meta.name }}
</p>
<img :src="article.meta.hero.meta.url" :alt="article.meta.hero.meta.alt" />
</article>
</template>
For lists:
<script setup>
import { useDocuments } from 'mikser-io-sdk-vue'
// All articles for /authors/dick, with each article's hero image inlined.
const { documents: articles } = useDocuments(() => ({
filter: { 'meta.component': 'article', 'meta.author': '/authors/dick' },
sort: { 'meta.date': -1 },
expand: ['hero'],
}))
</script>
<template>
<ul>
<li v-for="a in articles" :key="a.id">
<img v-if="a.meta.hero" :src="a.meta.hero.meta.url" :alt="a.meta.hero.meta.alt" />
<h2>{{ a.meta.title }}</h2>
</li>
</ul>
</template>
Path forms: dot-notation walks expanded entities (author.organization); * iterates $-keyed arrays (sections.*.image); both canonical ($author) and normalized (author) segments are accepted.
Server caps default to maxDepth: 5, maxPaths: 20, maxResolved: 100 per request — configurable via catalog.expand.{...} in mikser.config.js. Exceeding any cap surfaces as a MikserError with status === 422 on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level runtime.refs.subscribeGraph against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying mikser-io-sdk-api client:
import { useMikserClient } from 'mikser-io-sdk-vue'
const client = useMikserClient()
const { items } = await client.entities('public').list({
filter: { id: props.id },
expand: ['author.organization', 'hero'],
})
Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — createReactiveCache
The composables above (useDocument, useHref / meta) live inside a component's inject context — call them in setup, never in a Pinia store action or a plain module. createReactiveCache is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain .js modules, anywhere outside a component — because it carries its own state instead of reaching for inject. Create it once and share the instance.
It's a thin Vue shell around mikser-io-sdk-api's framework-agnostic createCache. The win Vue adds is the sync, reactive read: documentSync() returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's subscribe to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
// lib/mikser.js — module scope, no component context needed
import { createReactiveCache } from 'mikser-io-sdk-vue'
import { client } from './client.js'
export const content = createReactiveCache(client.entities('public'))
// a Pinia store
import { defineStore } from 'pinia'
import { content } from '../lib/mikser.js'
export const useSystemStore = defineStore('system', {
actions: {
// async — load + memoize. Use from actions.
loadProducts() {
return content.document('/system/products', { expand: ['products.*.video'] })
},
},
getters: {
// sync + reactive — re-evaluates when the load lands. Use from getters.
translationErrors: () => content.documentSync('/system/translation')?.meta.errors,
},
})
API
const content = createReactiveCache(client.entities('public')) returns:
Member
What it does
content.document(href, { expand } = {})
async — loads + memoizes the doc at that logical meta.href, resolves to the doc (items[0]) or null. References resolve by default ($ wildcard); pass expand: [] to opt out or a path list to narrow. Use from store actions.
content.documentSync(href, { expand } = {})
sync + reactive — returns the loaded doc or null, and re-evaluates when the load lands. Same default expand as document(). Use from Pinia getters / sync template helpers — this is the half composables can't do.
content.load(query, opts) / content.read(query)
The same async / sync pair, but for arbitrary list queries (returns the full envelope).
content.invalidate(query?)
Drop one entry, or the whole cache when called with no argument.
content.cache
The underlying mikser-io-sdk-api cache, if you need it.
The cache key folds in expand — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (useHref / meta), it doesn't replace it:
meta(ref) (live href index)
createReactiveCache
Freshness
Always-fresh — backed by an SSE subscription
Load-once — re-fetch only on invalidate
expand
No
Yes
Readable from
Components only (it's a composable)
Anywhere — stores, modules, components
Best for
Changing feeds, anything edited live
System docs, nav, settings — read often, change rarely
Reach for live() / meta() when the content changes under you and the UI must track it. Reach for createReactiveCache for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — createMikserVectorPlugin + useSimilar
Bridges the mikser-io-sdk-vector client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
// main.js — install the vector plugin alongside the documents one
import { createClient as createVectorClient } from 'mikser-io-sdk-vector'
import { createMikserVectorPlugin } from 'mikser-io-sdk-vue'
const similar = createVectorClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
app.use(createMikserVectorPlugin({ client: similar }))
<!-- components/SearchBox.vue -->
<script setup>
import { ref } from 'vue'
import { useSimilar } from 'mikser-io-sdk-vue'
const q = ref('')
const { results, loading } = useSimilar('documents', () => q.value, {
limit: 10,
debounce: 200, // ms after the last keystroke before firing
minLength: 2, // skip the request below this length
})
</script>
<template>
<input v-model="q" placeholder="Search…" />
<p v-if="loading">Searching…</p>
<ul>
<li v-for="hit in results" :key="hit.id">
<a :href="hit.id">{{ hit.data?.title }}</a>
<small>distance: {{ hit.distance.toFixed(3) }}</small>
</li>
</ul>
</template>
results is reactive; updates push to <v-for> in place.
loading is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
error is populated when findSimilar() rejects. Branch on it to render a fallback.
refresh() forces a fresh request against the current query. Use after the vector store has been updated server-side.
mikser-io-sdk-vector is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
npm install mikser-io-sdk-vector
The hit shape is generic on the embedded payload — type the data field for typed templates:
type ProductHit = { title: string; sku: string; price: number }
const { results } = useSimilar<ProductHit>('products', () => q.value)
// ↑ results.value[0].data is now ProductHit
What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A
DocumentPage component. Every project renders content differently (markdown engine, layouts, sanitization). useDocument() gives you the data; you write the component.
- A markdown renderer. Use
markdown-it, marked, or whatever your project standardizes on. If you render Markdown server-side via the render-markdown plugin, document.content is already HTML.
- A layout switcher. Some teams use
<router-view :class="$route.meta.layout">; some use named layouts; some pick via <component :is="...">. Stay neutral.
- Auth / token UI. Tokens are configuration —
createClient({ token }) handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
import type { ListEnvelope } from 'mikser-io-sdk-api'
interface Article {
id: string
meta: { title: string; date: string; summary?: string }
}
const { documents } = useDocuments<Article>({
filter: { 'meta.collection': 'articles' },
})
// documents.value is Ref<Article[]>; meta.title autocompletes.
The router-related types are forwarded from vue-router so mapRoute returns RouteRecordRaw directly.
License
MIT
]
The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the __INLINE_CODE_187__. __INLINE_CODE_188__ is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
__CODE_BLOCK_25__
__CODE_BLOCK_26__
One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__
- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__
Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
Field
Purpose
Example
__INLINE_CODE_221__
Logical reference — the same across all translations
__INLINE_CODE_222__
__INLINE_CODE_223__
Which language this translation represents
__INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__
__INLINE_CODE_228__
The deployed URL — what users actually navigate to
__INLINE_CODE_229__, __INLINE_CODE_230__
__CODE_BLOCK_29__
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__
The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__
Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32__
__INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__
The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__
The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__
Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__
Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
Call
Index state
Result
Why
__INLINE_CODE_269__
__INLINE_CODE_270__
__INLINE_CODE_271__
Exact match
__INLINE_CODE_272__
Same (no DE document exists)
__INLINE_CODE_273__ (or the first available)
Falls back to any available language so the link still works
__INLINE_CODE_274__
__INLINE_CODE_275__
__INLINE_CODE_276__
Single-language documents live in __INLINE_CODE_277__; matches across any locale request
__INLINE_CODE_278__
__INLINE_CODE_279__
__INLINE_CODE_280__
Locale-specific wins over default when present
__INLINE_CODE_281__
Not in index
__INLINE_CODE_282__
Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them.
__INLINE_CODE_283__ (no lang passed)
—
Uses __INLINE_CODE_284__ from __INLINE_CODE_285__
The composable's default lang
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__
Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__
Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39__
__INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__
__CODE_BLOCK_41__
Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42__
__INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__
For lists:
__CODE_BLOCK_44__
Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__
Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__
__CODE_BLOCK_47__
API
__INLINE_CODE_371__ returns:
Member
What it does
__INLINE_CODE_372__
async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions.
__INLINE_CODE_378__
sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do.
__INLINE_CODE_381__ / __INLINE_CODE_382__
The same async / sync pair, but for arbitrary list queries (returns the full envelope).
__INLINE_CODE_383__
Drop one entry, or the whole cache when called with no argument.
__INLINE_CODE_384__
The underlying __INLINE_CODE_385__ cache, if you need it.
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
__INLINE_CODE_389__ (live href index)
__INLINE_CODE_390__
Freshness
Always-fresh — backed by an SSE subscription
Load-once — re-fetch only on __INLINE_CODE_391__
__INLINE_CODE_392__
No
Yes
Readable from
Components only (it's a composable)
Anywhere — stores, modules, components
Best for
Changing feeds, anything edited live
System docs, nav, settings — read often, change rarely
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__
__CODE_BLOCK_49__
- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__
The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__
What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__
The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]: refs resolved
// a child component → useCurrentDocument() // reads whichever provide is nearest
One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
| Field | Purpose | Example |
|---|---|---|
| __INLINE_CODE_221__ | Logical reference — the same across all translations | __INLINE_CODE_222__ |
| __INLINE_CODE_223__ | Which language this translation represents | __INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__ |
| __INLINE_CODE_228__ | The deployed URL — what users actually navigate to | __INLINE_CODE_229__, __INLINE_CODE_230__ |
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32____INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
| Call | Index state | Result | Why |
|---|---|---|---|
| __INLINE_CODE_269__ | __INLINE_CODE_270__ | __INLINE_CODE_271__ | Exact match |
| __INLINE_CODE_272__ | Same (no DE document exists) | __INLINE_CODE_273__ (or the first available) | Falls back to any available language so the link still works |
| __INLINE_CODE_274__ | __INLINE_CODE_275__ | __INLINE_CODE_276__ | Single-language documents live in __INLINE_CODE_277__; matches across any locale request |
| __INLINE_CODE_278__ | __INLINE_CODE_279__ | __INLINE_CODE_280__ | Locale-specific wins over default when present |
| __INLINE_CODE_281__ | Not in index | __INLINE_CODE_282__ | Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them. |
| __INLINE_CODE_283__ (no lang passed) | — | Uses __INLINE_CODE_284__ from __INLINE_CODE_285__ | The composable's default lang |
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39____INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__ __CODE_BLOCK_41__Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42____INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__For lists:
__CODE_BLOCK_44__Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__ __CODE_BLOCK_47__API
__INLINE_CODE_371__ returns:
| Member | What it does |
|---|---|
| __INLINE_CODE_372__ | async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions. |
| __INLINE_CODE_378__ | sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do. |
| __INLINE_CODE_381__ / __INLINE_CODE_382__ | The same async / sync pair, but for arbitrary list queries (returns the full envelope). |
| __INLINE_CODE_383__ | Drop one entry, or the whole cache when called with no argument. |
| __INLINE_CODE_384__ | The underlying __INLINE_CODE_385__ cache, if you need it. |
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
| __INLINE_CODE_389__ (live href index) | __INLINE_CODE_390__ | |
|---|---|---|
| Freshness | Always-fresh — backed by an SSE subscription | Load-once — re-fetch only on __INLINE_CODE_391__ |
| __INLINE_CODE_392__ | No | Yes |
| Readable from | Components only (it's a composable) | Anywhere — stores, modules, components |
| Best for | Changing feeds, anything edited live | System docs, nav, settings — read often, change rarely |
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__ __CODE_BLOCK_49__- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the __INLINE_CODE_187__. __INLINE_CODE_188__ is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
__CODE_BLOCK_25__ __CODE_BLOCK_26__One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
| Field | Purpose | Example |
|---|---|---|
| __INLINE_CODE_221__ | Logical reference — the same across all translations | __INLINE_CODE_222__ |
| __INLINE_CODE_223__ | Which language this translation represents | __INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__ |
| __INLINE_CODE_228__ | The deployed URL — what users actually navigate to | __INLINE_CODE_229__, __INLINE_CODE_230__ |
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32____INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
| Call | Index state | Result | Why |
|---|---|---|---|
| __INLINE_CODE_269__ | __INLINE_CODE_270__ | __INLINE_CODE_271__ | Exact match |
| __INLINE_CODE_272__ | Same (no DE document exists) | __INLINE_CODE_273__ (or the first available) | Falls back to any available language so the link still works |
| __INLINE_CODE_274__ | __INLINE_CODE_275__ | __INLINE_CODE_276__ | Single-language documents live in __INLINE_CODE_277__; matches across any locale request |
| __INLINE_CODE_278__ | __INLINE_CODE_279__ | __INLINE_CODE_280__ | Locale-specific wins over default when present |
| __INLINE_CODE_281__ | Not in index | __INLINE_CODE_282__ | Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them. |
| __INLINE_CODE_283__ (no lang passed) | — | Uses __INLINE_CODE_284__ from __INLINE_CODE_285__ | The composable's default lang |
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39____INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__ __CODE_BLOCK_41__Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42____INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__For lists:
__CODE_BLOCK_44__Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__ __CODE_BLOCK_47__API
__INLINE_CODE_371__ returns:
| Member | What it does |
|---|---|
| __INLINE_CODE_372__ | async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions. |
| __INLINE_CODE_378__ | sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do. |
| __INLINE_CODE_381__ / __INLINE_CODE_382__ | The same async / sync pair, but for arbitrary list queries (returns the full envelope). |
| __INLINE_CODE_383__ | Drop one entry, or the whole cache when called with no argument. |
| __INLINE_CODE_384__ | The underlying __INLINE_CODE_385__ cache, if you need it. |
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
| __INLINE_CODE_389__ (live href index) | __INLINE_CODE_390__ | |
|---|---|---|
| Freshness | Always-fresh — backed by an SSE subscription | Load-once — re-fetch only on __INLINE_CODE_391__ |
| __INLINE_CODE_392__ | No | Yes |
| Readable from | Components only (it's a composable) | Anywhere — stores, modules, components |
| Best for | Changing feeds, anything edited live | System docs, nav, settings — read often, change rarely |
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__ __CODE_BLOCK_49__- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]:// views/Home.vue — renders the presentation + FAQ videos
import { provideCurrentDocument } from 'mikser-io-sdk-vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const { document } = provideCurrentDocument({ route: () => route.path }) // default expand: ['
The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the __INLINE_CODE_187__. __INLINE_CODE_188__ is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
// composables/document.js
import { useRoute } from 'vue-router'
import { provideCurrentDocument } from 'mikser-io-sdk-vue'
import { client } from '../lib/mikser'
// A view owns its document load; references resolve by default ($ wildcard).
// Pass [] to opt out, or a path list to narrow.
export function provideRouteDocument(expand) {
const route = useRoute()
return provideCurrentDocument({ client, route: () => route.path, expand })
}
// App.vue → provideRouteDocument([]) // neutral: head + plain fields
// views/Home.vue (renders refs) → provideRouteDocument() // default ['
One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createClient } from 'mikser-io-sdk-api'
import { createMikserPlugin, useMikserRoutes } from 'mikser-io-sdk-vue'
import App from './App.vue'
// One client. `data.catalog` pulls the narrow snapshot the data plugin
// writes — fast first paint, CDN-cacheable — then live SSE keeps it
// current.
const documents = createClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
.entities('public', { data: { catalog: 'sitemap', entities: 'page' } })
// Your router. Your routes. Your guards.
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/login', name: 'login', component: () => import('./views/Login.vue') },
{ path: '/dashboard', name: 'dashboard', component: () => import('./views/Dashboard.vue') },
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('./views/NotFound.vue') },
],
})
// Plug mikser into the same router. Await seeded before mount.
const { seeded } = useMikserRoutes(router, {
mapRoute: document => ({
path: document.meta.route,
name: document.id, // required — used as diff key
component: () => import('./views/DocumentPage.vue'),
props: { entityId: document.id },
}),
})
await seeded
createApp(App)
.use(createMikserPlugin({ client: documents }))
.use(router)
.mount('#app')
- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
// build/generate-routes.mjs
import { writeFile } from 'node:fs/promises'
import { createClient } from 'mikser-io-sdk-api'
import { generateMikserRoutes } from 'mikser-io-sdk-vue'
const documents = createClient({ baseUrl: process.env.MIKSER_URL }).entities('public')
const routes = await generateMikserRoutes({
client: documents,
mapRoute: document => ({ path: document.meta.route, name: document.id, /* … */ }),
})
await writeFile('./src/generated/routes.json', JSON.stringify(routes, null, 2))
Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
Field
Purpose
Example
__INLINE_CODE_221__
Logical reference — the same across all translations
__INLINE_CODE_222__
__INLINE_CODE_223__
Which language this translation represents
__INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__
__INLINE_CODE_228__
The deployed URL — what users actually navigate to
__INLINE_CODE_229__, __INLINE_CODE_230__
# documents/en/about.md
---
title: About us
href: /about # logical reference
lang: en # which language
route: /en/about # deployed URL
---
# documents/fr/a-propos.md
---
title: À propos
href: /about # same logical ref — this is the FR translation of "about"
lang: fr
route: /fr/a-propos # localised slug — the URL the browser navigates to
---
# documents/de/uber-uns.md
---
title: Über uns
href: /about # same logical ref
lang: de
route: /de/uber-uns
---
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
<!-- App.vue -->
<script setup>
import { provideHrefIndex } from 'mikser-io-sdk-vue'
provideHrefIndex() // uses the injected client
</script>
<template>
<router-view />
</template>
The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n() // your existing i18n state
const { href } = useHref(locale) // reactive — re-resolves when locale changes
</script>
<template>
<nav>
<router-link :to="href('/')">Home</router-link>
<router-link :to="href('/about')">About</router-link>
<router-link :to="href('/contact')">Contact</router-link>
<!-- Explicit override — show this link in French regardless of current locale -->
<a :href="href('/about', 'fr')">Voir en français</a>
</nav>
</template>
Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
const { href, meta } = useHref()
</script>
<template>
<!-- href(ref) → URL ; meta(ref) → that document's content. Both reactive. -->
<ul class="menu">
<li v-for="item in meta('/menu')?.products" :key="item.id">
<router-link :to="href(item.ref)">{{ item.name }}</router-link>
</li>
</ul>
</template>
__INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
<!-- components/LanguageSwitcher.vue -->
<script setup>
import { useRoute } from 'vue-router'
import { useAlternates } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const route = useRoute()
const { locale, availableLocales } = useI18n()
// Explicit language list — show every locale the app supports, even
// for pages where a translation doesn't exist yet (the SDK falls back
// via href()'s resolution chain). Right shape for a switcher.
const { alternates } = useAlternates({
route: () => route.path,
languages: availableLocales,
})
</script>
<template>
<ul class="lang-switcher">
<li v-for="alt in alternates" :key="alt.lang">
<router-link :to="alt.url">{{ alt.lang.toUpperCase() }}</router-link>
</li>
</ul>
</template>
The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
const { href } = useHref(locale)
const crumbs = [
{ ref: '/', label: 'Home' },
{ ref: '/products', label: 'Products' },
{ ref: '/products/sofa-line', label: 'Sofa Line' },
]
</script>
<template>
<nav class="crumbs">
<router-link v-for="c in crumbs" :key="c.ref" :to="href(c.ref)">
{{ c.label }}
</router-link>
</nav>
</template>
The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
<script setup>
import { useDocuments } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
// Query for posts in the current language.
const { documents: posts } = useDocuments(() => ({
filter: { 'meta.collection': 'posts', 'meta.lang': locale.value },
sort: { 'meta.date': -1 },
fields: ['id', 'meta.title', 'meta.route'],
limit: 20,
}))
</script>
<template>
<ul>
<li v-for="post in posts" :key="post.id">
<router-link :to="post.meta.route">{{ post.meta.title }}</router-link>
</li>
</ul>
</template>
Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
import { useRouter } from 'vue-router'
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const router = useRouter()
const { locale } = useI18n()
const { href } = useHref(locale)
async function onSubmit() {
await saveForm()
router.push(href('/thank-you'))
}
Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
Call
Index state
Result
Why
__INLINE_CODE_269__
__INLINE_CODE_270__
__INLINE_CODE_271__
Exact match
__INLINE_CODE_272__
Same (no DE document exists)
__INLINE_CODE_273__ (or the first available)
Falls back to any available language so the link still works
__INLINE_CODE_274__
__INLINE_CODE_275__
__INLINE_CODE_276__
Single-language documents live in __INLINE_CODE_277__; matches across any locale request
__INLINE_CODE_278__
__INLINE_CODE_279__
__INLINE_CODE_280__
Locale-specific wins over default when present
__INLINE_CODE_281__
Not in index
__INLINE_CODE_282__
Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them.
__INLINE_CODE_283__ (no lang passed)
—
Uses __INLINE_CODE_284__ from __INLINE_CODE_285__
The composable's default lang
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
<!-- AppHead.vue (use with Vue Meta / Unhead / Vue 3's built-in head support) -->
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useHead } from '@unhead/vue'
import { useAlternates } from 'mikser-io-sdk-vue'
const route = useRoute()
// No `languages` arg — return only translations that actually exist.
// Right shape for hreflang: don't advertise pages we don't have.
const { alternates } = useAlternates({ route: () => route.path })
useHead({
link: computed(() => alternates.value.map(({ lang, url }) => ({
rel: 'alternate',
hreflang: lang,
href: url,
}))),
})
</script>
Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
// build/generate-href-index.mjs
import { writeFile } from 'node:fs/promises'
import { createClient } from 'mikser-io-sdk-api'
const documents = createClient({ baseUrl: process.env.MIKSER_URL }).entities('public')
const { items } = await documents.list({
filter: { 'meta.href': { $exists: true } },
fields: ['id', 'meta'],
limit: 10_000,
})
const index = {}
for (const d of items) {
const ref = d.meta?.href, lang = d.meta?.lang ?? 'default'
const url = d.meta?.route ?? d.meta?.destination ?? ref
if (!ref) continue
if (!index[ref]) index[ref] = {}
index[ref][lang] = url
}
await writeFile('./src/generated/href-index.json', JSON.stringify(index, null, 2))
Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
<!-- components/Clip.vue -->
<script setup>
import { useAsset } from 'mikser-io-sdk-vue'
const { url } = useAsset()
</script>
<template>
<video :src="url(clip.meta.url)"
:poster="url(clip.meta.presets.poster)"></video>
</template>
__INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
<!-- App.vue -->
<script setup>
import { provideAssetIndex } from 'mikser-io-sdk-vue'
provideAssetIndex()
</script>
const { asset } = useAsset()
const { url, meta } = asset('/assets/hero.jpg') ?? {}
Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps(['id'])
const { document: article } = useDocument(() => props.id)
const { document: author } = useDocument(() => article.value?.meta.author)
</script>
<template>
<article v-if="article && author">
<h1>{{ article.meta.title }}</h1>
<p>By {{ author.meta.name }}</p>
</article>
</template>
__INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps(['id'])
// Single call returns article + author + author.organization + hero
// in one round-trip. The initial paint shows fully-resolved data.
const { document: article } = useDocument(() => props.id, {
expand: ['author.organization', 'hero'],
})
</script>
<template>
<article v-if="article">
<h1>{{ article.meta.title }}</h1>
<p>
By {{ article.meta.author.meta.name }}
— {{ article.meta.author.meta.organization.meta.name }}
</p>
<img :src="article.meta.hero.meta.url" :alt="article.meta.hero.meta.alt" />
</article>
</template>
For lists:
<script setup>
import { useDocuments } from 'mikser-io-sdk-vue'
// All articles for /authors/dick, with each article's hero image inlined.
const { documents: articles } = useDocuments(() => ({
filter: { 'meta.component': 'article', 'meta.author': '/authors/dick' },
sort: { 'meta.date': -1 },
expand: ['hero'],
}))
</script>
<template>
<ul>
<li v-for="a in articles" :key="a.id">
<img v-if="a.meta.hero" :src="a.meta.hero.meta.url" :alt="a.meta.hero.meta.alt" />
<h2>{{ a.meta.title }}</h2>
</li>
</ul>
</template>
Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
import { useMikserClient } from 'mikser-io-sdk-vue'
const client = useMikserClient()
const { items } = await client.entities('public').list({
filter: { id: props.id },
expand: ['author.organization', 'hero'],
})
Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
// lib/mikser.js — module scope, no component context needed
import { createReactiveCache } from 'mikser-io-sdk-vue'
import { client } from './client.js'
export const content = createReactiveCache(client.entities('public'))
// a Pinia store
import { defineStore } from 'pinia'
import { content } from '../lib/mikser.js'
export const useSystemStore = defineStore('system', {
actions: {
// async — load + memoize. Use from actions.
loadProducts() {
return content.document('/system/products', { expand: ['products.*.video'] })
},
},
getters: {
// sync + reactive — re-evaluates when the load lands. Use from getters.
translationErrors: () => content.documentSync('/system/translation')?.meta.errors,
},
})
API
__INLINE_CODE_371__ returns:
Member
What it does
__INLINE_CODE_372__
async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions.
__INLINE_CODE_378__
sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do.
__INLINE_CODE_381__ / __INLINE_CODE_382__
The same async / sync pair, but for arbitrary list queries (returns the full envelope).
__INLINE_CODE_383__
Drop one entry, or the whole cache when called with no argument.
__INLINE_CODE_384__
The underlying __INLINE_CODE_385__ cache, if you need it.
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
__INLINE_CODE_389__ (live href index)
__INLINE_CODE_390__
Freshness
Always-fresh — backed by an SSE subscription
Load-once — re-fetch only on __INLINE_CODE_391__
__INLINE_CODE_392__
No
Yes
Readable from
Components only (it's a composable)
Anywhere — stores, modules, components
Best for
Changing feeds, anything edited live
System docs, nav, settings — read often, change rarely
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
// main.js — install the vector plugin alongside the documents one
import { createClient as createVectorClient } from 'mikser-io-sdk-vector'
import { createMikserVectorPlugin } from 'mikser-io-sdk-vue'
const similar = createVectorClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
app.use(createMikserVectorPlugin({ client: similar }))
<!-- components/SearchBox.vue -->
<script setup>
import { ref } from 'vue'
import { useSimilar } from 'mikser-io-sdk-vue'
const q = ref('')
const { results, loading } = useSimilar('documents', () => q.value, {
limit: 10,
debounce: 200, // ms after the last keystroke before firing
minLength: 2, // skip the request below this length
})
</script>
<template>
<input v-model="q" placeholder="Search…" />
<p v-if="loading">Searching…</p>
<ul>
<li v-for="hit in results" :key="hit.id">
<a :href="hit.id">{{ hit.data?.title }}</a>
<small>distance: {{ hit.distance.toFixed(3) }}</small>
</li>
</ul>
</template>
- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
npm install mikser-io-sdk-vector
The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
type ProductHit = { title: string; sku: string; price: number }
const { results } = useSimilar<ProductHit>('products', () => q.value)
// ↑ results.value[0].data is now ProductHit
What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
import type { ListEnvelope } from 'mikser-io-sdk-api'
interface Article {
id: string
meta: { title: string; date: string; summary?: string }
}
const { documents } = useDocuments<Article>({
filter: { 'meta.collection': 'articles' },
})
// documents.value is Ref<Article[]>; meta.title autocompletes.
The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]
The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the __INLINE_CODE_187__. __INLINE_CODE_188__ is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
__CODE_BLOCK_25__
__CODE_BLOCK_26__
One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__
- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__
Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
Field
Purpose
Example
__INLINE_CODE_221__
Logical reference — the same across all translations
__INLINE_CODE_222__
__INLINE_CODE_223__
Which language this translation represents
__INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__
__INLINE_CODE_228__
The deployed URL — what users actually navigate to
__INLINE_CODE_229__, __INLINE_CODE_230__
__CODE_BLOCK_29__
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__
The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__
Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32__
__INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__
The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__
The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__
Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__
Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
Call
Index state
Result
Why
__INLINE_CODE_269__
__INLINE_CODE_270__
__INLINE_CODE_271__
Exact match
__INLINE_CODE_272__
Same (no DE document exists)
__INLINE_CODE_273__ (or the first available)
Falls back to any available language so the link still works
__INLINE_CODE_274__
__INLINE_CODE_275__
__INLINE_CODE_276__
Single-language documents live in __INLINE_CODE_277__; matches across any locale request
__INLINE_CODE_278__
__INLINE_CODE_279__
__INLINE_CODE_280__
Locale-specific wins over default when present
__INLINE_CODE_281__
Not in index
__INLINE_CODE_282__
Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them.
__INLINE_CODE_283__ (no lang passed)
—
Uses __INLINE_CODE_284__ from __INLINE_CODE_285__
The composable's default lang
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__
Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__
Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39__
__INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__
__CODE_BLOCK_41__
Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42__
__INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__
For lists:
__CODE_BLOCK_44__
Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__
Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__
__CODE_BLOCK_47__
API
__INLINE_CODE_371__ returns:
Member
What it does
__INLINE_CODE_372__
async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions.
__INLINE_CODE_378__
sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do.
__INLINE_CODE_381__ / __INLINE_CODE_382__
The same async / sync pair, but for arbitrary list queries (returns the full envelope).
__INLINE_CODE_383__
Drop one entry, or the whole cache when called with no argument.
__INLINE_CODE_384__
The underlying __INLINE_CODE_385__ cache, if you need it.
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
__INLINE_CODE_389__ (live href index)
__INLINE_CODE_390__
Freshness
Always-fresh — backed by an SSE subscription
Load-once — re-fetch only on __INLINE_CODE_391__
__INLINE_CODE_392__
No
Yes
Readable from
Components only (it's a composable)
Anywhere — stores, modules, components
Best for
Changing feeds, anything edited live
System docs, nav, settings — read often, change rarely
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__
__CODE_BLOCK_49__
- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__
The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__
What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__
The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]: refs resolved
// a child component → useCurrentDocument() // reads whichever provide is nearest
One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
| Field | Purpose | Example |
|---|---|---|
| __INLINE_CODE_221__ | Logical reference — the same across all translations | __INLINE_CODE_222__ |
| __INLINE_CODE_223__ | Which language this translation represents | __INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__ |
| __INLINE_CODE_228__ | The deployed URL — what users actually navigate to | __INLINE_CODE_229__, __INLINE_CODE_230__ |
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32____INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
| Call | Index state | Result | Why |
|---|---|---|---|
| __INLINE_CODE_269__ | __INLINE_CODE_270__ | __INLINE_CODE_271__ | Exact match |
| __INLINE_CODE_272__ | Same (no DE document exists) | __INLINE_CODE_273__ (or the first available) | Falls back to any available language so the link still works |
| __INLINE_CODE_274__ | __INLINE_CODE_275__ | __INLINE_CODE_276__ | Single-language documents live in __INLINE_CODE_277__; matches across any locale request |
| __INLINE_CODE_278__ | __INLINE_CODE_279__ | __INLINE_CODE_280__ | Locale-specific wins over default when present |
| __INLINE_CODE_281__ | Not in index | __INLINE_CODE_282__ | Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them. |
| __INLINE_CODE_283__ (no lang passed) | — | Uses __INLINE_CODE_284__ from __INLINE_CODE_285__ | The composable's default lang |
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39____INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__ __CODE_BLOCK_41__Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42____INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__For lists:
__CODE_BLOCK_44__Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__ __CODE_BLOCK_47__API
__INLINE_CODE_371__ returns:
| Member | What it does |
|---|---|
| __INLINE_CODE_372__ | async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions. |
| __INLINE_CODE_378__ | sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do. |
| __INLINE_CODE_381__ / __INLINE_CODE_382__ | The same async / sync pair, but for arbitrary list queries (returns the full envelope). |
| __INLINE_CODE_383__ | Drop one entry, or the whole cache when called with no argument. |
| __INLINE_CODE_384__ | The underlying __INLINE_CODE_385__ cache, if you need it. |
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
| __INLINE_CODE_389__ (live href index) | __INLINE_CODE_390__ | |
|---|---|---|
| Freshness | Always-fresh — backed by an SSE subscription | Load-once — re-fetch only on __INLINE_CODE_391__ |
| __INLINE_CODE_392__ | No | Yes |
| Readable from | Components only (it's a composable) | Anywhere — stores, modules, components |
| Best for | Changing feeds, anything edited live | System docs, nav, settings — read often, change rarely |
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__ __CODE_BLOCK_49__- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the __INLINE_CODE_187__. __INLINE_CODE_188__ is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
__CODE_BLOCK_25__ __CODE_BLOCK_26__One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
| Field | Purpose | Example |
|---|---|---|
| __INLINE_CODE_221__ | Logical reference — the same across all translations | __INLINE_CODE_222__ |
| __INLINE_CODE_223__ | Which language this translation represents | __INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__ |
| __INLINE_CODE_228__ | The deployed URL — what users actually navigate to | __INLINE_CODE_229__, __INLINE_CODE_230__ |
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32____INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
| Call | Index state | Result | Why |
|---|---|---|---|
| __INLINE_CODE_269__ | __INLINE_CODE_270__ | __INLINE_CODE_271__ | Exact match |
| __INLINE_CODE_272__ | Same (no DE document exists) | __INLINE_CODE_273__ (or the first available) | Falls back to any available language so the link still works |
| __INLINE_CODE_274__ | __INLINE_CODE_275__ | __INLINE_CODE_276__ | Single-language documents live in __INLINE_CODE_277__; matches across any locale request |
| __INLINE_CODE_278__ | __INLINE_CODE_279__ | __INLINE_CODE_280__ | Locale-specific wins over default when present |
| __INLINE_CODE_281__ | Not in index | __INLINE_CODE_282__ | Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them. |
| __INLINE_CODE_283__ (no lang passed) | — | Uses __INLINE_CODE_284__ from __INLINE_CODE_285__ | The composable's default lang |
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39____INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__ __CODE_BLOCK_41__Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42____INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__For lists:
__CODE_BLOCK_44__Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__ __CODE_BLOCK_47__API
__INLINE_CODE_371__ returns:
| Member | What it does |
|---|---|
| __INLINE_CODE_372__ | async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions. |
| __INLINE_CODE_378__ | sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do. |
| __INLINE_CODE_381__ / __INLINE_CODE_382__ | The same async / sync pair, but for arbitrary list queries (returns the full envelope). |
| __INLINE_CODE_383__ | Drop one entry, or the whole cache when called with no argument. |
| __INLINE_CODE_384__ | The underlying __INLINE_CODE_385__ cache, if you need it. |
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
| __INLINE_CODE_389__ (live href index) | __INLINE_CODE_390__ | |
|---|---|---|
| Freshness | Always-fresh — backed by an SSE subscription | Load-once — re-fetch only on __INLINE_CODE_391__ |
| __INLINE_CODE_392__ | No | Yes |
| Readable from | Components only (it's a composable) | Anywhere — stores, modules, components |
| Best for | Changing feeds, anything edited live | System docs, nav, settings — read often, change rarely |
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__ __CODE_BLOCK_49__- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
] is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
// composables/document.js
import { useRoute } from 'vue-router'
import { provideCurrentDocument } from 'mikser-io-sdk-vue'
import { client } from '../lib/mikser'
// A view owns its document load; references resolve by default ($ wildcard).
// Pass [] to opt out, or a path list to narrow.
export function provideRouteDocument(expand) {
const route = useRoute()
return provideCurrentDocument({ client, route: () => route.path, expand })
}
// App.vue → provideRouteDocument([]) // neutral: head + plain fields
// views/Home.vue (renders refs) → provideRouteDocument() // default ['
One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createClient } from 'mikser-io-sdk-api'
import { createMikserPlugin, useMikserRoutes } from 'mikser-io-sdk-vue'
import App from './App.vue'
// One client. `data.catalog` pulls the narrow snapshot the data plugin
// writes — fast first paint, CDN-cacheable — then live SSE keeps it
// current.
const documents = createClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
.entities('public', { data: { catalog: 'sitemap', entities: 'page' } })
// Your router. Your routes. Your guards.
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/login', name: 'login', component: () => import('./views/Login.vue') },
{ path: '/dashboard', name: 'dashboard', component: () => import('./views/Dashboard.vue') },
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('./views/NotFound.vue') },
],
})
// Plug mikser into the same router. Await seeded before mount.
const { seeded } = useMikserRoutes(router, {
mapRoute: document => ({
path: document.meta.route,
name: document.id, // required — used as diff key
component: () => import('./views/DocumentPage.vue'),
props: { entityId: document.id },
}),
})
await seeded
createApp(App)
.use(createMikserPlugin({ client: documents }))
.use(router)
.mount('#app')
- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
// build/generate-routes.mjs
import { writeFile } from 'node:fs/promises'
import { createClient } from 'mikser-io-sdk-api'
import { generateMikserRoutes } from 'mikser-io-sdk-vue'
const documents = createClient({ baseUrl: process.env.MIKSER_URL }).entities('public')
const routes = await generateMikserRoutes({
client: documents,
mapRoute: document => ({ path: document.meta.route, name: document.id, /* … */ }),
})
await writeFile('./src/generated/routes.json', JSON.stringify(routes, null, 2))
Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
Field
Purpose
Example
__INLINE_CODE_221__
Logical reference — the same across all translations
__INLINE_CODE_222__
__INLINE_CODE_223__
Which language this translation represents
__INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__
__INLINE_CODE_228__
The deployed URL — what users actually navigate to
__INLINE_CODE_229__, __INLINE_CODE_230__
# documents/en/about.md
---
title: About us
href: /about # logical reference
lang: en # which language
route: /en/about # deployed URL
---
# documents/fr/a-propos.md
---
title: À propos
href: /about # same logical ref — this is the FR translation of "about"
lang: fr
route: /fr/a-propos # localised slug — the URL the browser navigates to
---
# documents/de/uber-uns.md
---
title: Über uns
href: /about # same logical ref
lang: de
route: /de/uber-uns
---
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
<!-- App.vue -->
<script setup>
import { provideHrefIndex } from 'mikser-io-sdk-vue'
provideHrefIndex() // uses the injected client
</script>
<template>
<router-view />
</template>
The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n() // your existing i18n state
const { href } = useHref(locale) // reactive — re-resolves when locale changes
</script>
<template>
<nav>
<router-link :to="href('/')">Home</router-link>
<router-link :to="href('/about')">About</router-link>
<router-link :to="href('/contact')">Contact</router-link>
<!-- Explicit override — show this link in French regardless of current locale -->
<a :href="href('/about', 'fr')">Voir en français</a>
</nav>
</template>
Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
const { href, meta } = useHref()
</script>
<template>
<!-- href(ref) → URL ; meta(ref) → that document's content. Both reactive. -->
<ul class="menu">
<li v-for="item in meta('/menu')?.products" :key="item.id">
<router-link :to="href(item.ref)">{{ item.name }}</router-link>
</li>
</ul>
</template>
__INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
<!-- components/LanguageSwitcher.vue -->
<script setup>
import { useRoute } from 'vue-router'
import { useAlternates } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const route = useRoute()
const { locale, availableLocales } = useI18n()
// Explicit language list — show every locale the app supports, even
// for pages where a translation doesn't exist yet (the SDK falls back
// via href()'s resolution chain). Right shape for a switcher.
const { alternates } = useAlternates({
route: () => route.path,
languages: availableLocales,
})
</script>
<template>
<ul class="lang-switcher">
<li v-for="alt in alternates" :key="alt.lang">
<router-link :to="alt.url">{{ alt.lang.toUpperCase() }}</router-link>
</li>
</ul>
</template>
The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
const { href } = useHref(locale)
const crumbs = [
{ ref: '/', label: 'Home' },
{ ref: '/products', label: 'Products' },
{ ref: '/products/sofa-line', label: 'Sofa Line' },
]
</script>
<template>
<nav class="crumbs">
<router-link v-for="c in crumbs" :key="c.ref" :to="href(c.ref)">
{{ c.label }}
</router-link>
</nav>
</template>
The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
<script setup>
import { useDocuments } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
// Query for posts in the current language.
const { documents: posts } = useDocuments(() => ({
filter: { 'meta.collection': 'posts', 'meta.lang': locale.value },
sort: { 'meta.date': -1 },
fields: ['id', 'meta.title', 'meta.route'],
limit: 20,
}))
</script>
<template>
<ul>
<li v-for="post in posts" :key="post.id">
<router-link :to="post.meta.route">{{ post.meta.title }}</router-link>
</li>
</ul>
</template>
Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
import { useRouter } from 'vue-router'
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const router = useRouter()
const { locale } = useI18n()
const { href } = useHref(locale)
async function onSubmit() {
await saveForm()
router.push(href('/thank-you'))
}
Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
Call
Index state
Result
Why
__INLINE_CODE_269__
__INLINE_CODE_270__
__INLINE_CODE_271__
Exact match
__INLINE_CODE_272__
Same (no DE document exists)
__INLINE_CODE_273__ (or the first available)
Falls back to any available language so the link still works
__INLINE_CODE_274__
__INLINE_CODE_275__
__INLINE_CODE_276__
Single-language documents live in __INLINE_CODE_277__; matches across any locale request
__INLINE_CODE_278__
__INLINE_CODE_279__
__INLINE_CODE_280__
Locale-specific wins over default when present
__INLINE_CODE_281__
Not in index
__INLINE_CODE_282__
Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them.
__INLINE_CODE_283__ (no lang passed)
—
Uses __INLINE_CODE_284__ from __INLINE_CODE_285__
The composable's default lang
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
<!-- AppHead.vue (use with Vue Meta / Unhead / Vue 3's built-in head support) -->
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useHead } from '@unhead/vue'
import { useAlternates } from 'mikser-io-sdk-vue'
const route = useRoute()
// No `languages` arg — return only translations that actually exist.
// Right shape for hreflang: don't advertise pages we don't have.
const { alternates } = useAlternates({ route: () => route.path })
useHead({
link: computed(() => alternates.value.map(({ lang, url }) => ({
rel: 'alternate',
hreflang: lang,
href: url,
}))),
})
</script>
Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
// build/generate-href-index.mjs
import { writeFile } from 'node:fs/promises'
import { createClient } from 'mikser-io-sdk-api'
const documents = createClient({ baseUrl: process.env.MIKSER_URL }).entities('public')
const { items } = await documents.list({
filter: { 'meta.href': { $exists: true } },
fields: ['id', 'meta'],
limit: 10_000,
})
const index = {}
for (const d of items) {
const ref = d.meta?.href, lang = d.meta?.lang ?? 'default'
const url = d.meta?.route ?? d.meta?.destination ?? ref
if (!ref) continue
if (!index[ref]) index[ref] = {}
index[ref][lang] = url
}
await writeFile('./src/generated/href-index.json', JSON.stringify(index, null, 2))
Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
<!-- components/Clip.vue -->
<script setup>
import { useAsset } from 'mikser-io-sdk-vue'
const { url } = useAsset()
</script>
<template>
<video :src="url(clip.meta.url)"
:poster="url(clip.meta.presets.poster)"></video>
</template>
__INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
<!-- App.vue -->
<script setup>
import { provideAssetIndex } from 'mikser-io-sdk-vue'
provideAssetIndex()
</script>
const { asset } = useAsset()
const { url, meta } = asset('/assets/hero.jpg') ?? {}
Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps(['id'])
const { document: article } = useDocument(() => props.id)
const { document: author } = useDocument(() => article.value?.meta.author)
</script>
<template>
<article v-if="article && author">
<h1>{{ article.meta.title }}</h1>
<p>By {{ author.meta.name }}</p>
</article>
</template>
__INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps(['id'])
// Single call returns article + author + author.organization + hero
// in one round-trip. The initial paint shows fully-resolved data.
const { document: article } = useDocument(() => props.id, {
expand: ['author.organization', 'hero'],
})
</script>
<template>
<article v-if="article">
<h1>{{ article.meta.title }}</h1>
<p>
By {{ article.meta.author.meta.name }}
— {{ article.meta.author.meta.organization.meta.name }}
</p>
<img :src="article.meta.hero.meta.url" :alt="article.meta.hero.meta.alt" />
</article>
</template>
For lists:
<script setup>
import { useDocuments } from 'mikser-io-sdk-vue'
// All articles for /authors/dick, with each article's hero image inlined.
const { documents: articles } = useDocuments(() => ({
filter: { 'meta.component': 'article', 'meta.author': '/authors/dick' },
sort: { 'meta.date': -1 },
expand: ['hero'],
}))
</script>
<template>
<ul>
<li v-for="a in articles" :key="a.id">
<img v-if="a.meta.hero" :src="a.meta.hero.meta.url" :alt="a.meta.hero.meta.alt" />
<h2>{{ a.meta.title }}</h2>
</li>
</ul>
</template>
Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
import { useMikserClient } from 'mikser-io-sdk-vue'
const client = useMikserClient()
const { items } = await client.entities('public').list({
filter: { id: props.id },
expand: ['author.organization', 'hero'],
})
Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
// lib/mikser.js — module scope, no component context needed
import { createReactiveCache } from 'mikser-io-sdk-vue'
import { client } from './client.js'
export const content = createReactiveCache(client.entities('public'))
// a Pinia store
import { defineStore } from 'pinia'
import { content } from '../lib/mikser.js'
export const useSystemStore = defineStore('system', {
actions: {
// async — load + memoize. Use from actions.
loadProducts() {
return content.document('/system/products', { expand: ['products.*.video'] })
},
},
getters: {
// sync + reactive — re-evaluates when the load lands. Use from getters.
translationErrors: () => content.documentSync('/system/translation')?.meta.errors,
},
})
API
__INLINE_CODE_371__ returns:
Member
What it does
__INLINE_CODE_372__
async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions.
__INLINE_CODE_378__
sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do.
__INLINE_CODE_381__ / __INLINE_CODE_382__
The same async / sync pair, but for arbitrary list queries (returns the full envelope).
__INLINE_CODE_383__
Drop one entry, or the whole cache when called with no argument.
__INLINE_CODE_384__
The underlying __INLINE_CODE_385__ cache, if you need it.
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
__INLINE_CODE_389__ (live href index)
__INLINE_CODE_390__
Freshness
Always-fresh — backed by an SSE subscription
Load-once — re-fetch only on __INLINE_CODE_391__
__INLINE_CODE_392__
No
Yes
Readable from
Components only (it's a composable)
Anywhere — stores, modules, components
Best for
Changing feeds, anything edited live
System docs, nav, settings — read often, change rarely
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
// main.js — install the vector plugin alongside the documents one
import { createClient as createVectorClient } from 'mikser-io-sdk-vector'
import { createMikserVectorPlugin } from 'mikser-io-sdk-vue'
const similar = createVectorClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
app.use(createMikserVectorPlugin({ client: similar }))
<!-- components/SearchBox.vue -->
<script setup>
import { ref } from 'vue'
import { useSimilar } from 'mikser-io-sdk-vue'
const q = ref('')
const { results, loading } = useSimilar('documents', () => q.value, {
limit: 10,
debounce: 200, // ms after the last keystroke before firing
minLength: 2, // skip the request below this length
})
</script>
<template>
<input v-model="q" placeholder="Search…" />
<p v-if="loading">Searching…</p>
<ul>
<li v-for="hit in results" :key="hit.id">
<a :href="hit.id">{{ hit.data?.title }}</a>
<small>distance: {{ hit.distance.toFixed(3) }}</small>
</li>
</ul>
</template>
- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
npm install mikser-io-sdk-vector
The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
type ProductHit = { title: string; sku: string; price: number }
const { results } = useSimilar<ProductHit>('products', () => q.value)
// ↑ results.value[0].data is now ProductHit
What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
import type { ListEnvelope } from 'mikser-io-sdk-api'
interface Article {
id: string
meta: { title: string; date: string; summary?: string }
}
const { documents } = useDocuments<Article>({
filter: { 'meta.collection': 'articles' },
})
// documents.value is Ref<Article[]>; meta.title autocompletes.
The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]
The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the __INLINE_CODE_187__. __INLINE_CODE_188__ is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
__CODE_BLOCK_25__ __CODE_BLOCK_26__One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
| Field | Purpose | Example |
|---|---|---|
| __INLINE_CODE_221__ | Logical reference — the same across all translations | __INLINE_CODE_222__ |
| __INLINE_CODE_223__ | Which language this translation represents | __INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__ |
| __INLINE_CODE_228__ | The deployed URL — what users actually navigate to | __INLINE_CODE_229__, __INLINE_CODE_230__ |
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32____INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
| Call | Index state | Result | Why |
|---|---|---|---|
| __INLINE_CODE_269__ | __INLINE_CODE_270__ | __INLINE_CODE_271__ | Exact match |
| __INLINE_CODE_272__ | Same (no DE document exists) | __INLINE_CODE_273__ (or the first available) | Falls back to any available language so the link still works |
| __INLINE_CODE_274__ | __INLINE_CODE_275__ | __INLINE_CODE_276__ | Single-language documents live in __INLINE_CODE_277__; matches across any locale request |
| __INLINE_CODE_278__ | __INLINE_CODE_279__ | __INLINE_CODE_280__ | Locale-specific wins over default when present |
| __INLINE_CODE_281__ | Not in index | __INLINE_CODE_282__ | Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them. |
| __INLINE_CODE_283__ (no lang passed) | — | Uses __INLINE_CODE_284__ from __INLINE_CODE_285__ | The composable's default lang |
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39____INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__ __CODE_BLOCK_41__Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42____INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__For lists:
__CODE_BLOCK_44__Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__ __CODE_BLOCK_47__API
__INLINE_CODE_371__ returns:
| Member | What it does |
|---|---|
| __INLINE_CODE_372__ | async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions. |
| __INLINE_CODE_378__ | sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do. |
| __INLINE_CODE_381__ / __INLINE_CODE_382__ | The same async / sync pair, but for arbitrary list queries (returns the full envelope). |
| __INLINE_CODE_383__ | Drop one entry, or the whole cache when called with no argument. |
| __INLINE_CODE_384__ | The underlying __INLINE_CODE_385__ cache, if you need it. |
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
| __INLINE_CODE_389__ (live href index) | __INLINE_CODE_390__ | |
|---|---|---|
| Freshness | Always-fresh — backed by an SSE subscription | Load-once — re-fetch only on __INLINE_CODE_391__ |
| __INLINE_CODE_392__ | No | Yes |
| Readable from | Components only (it's a composable) | Anywhere — stores, modules, components |
| Best for | Changing feeds, anything edited live | System docs, nav, settings — read often, change rarely |
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__ __CODE_BLOCK_49__- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]: refs resolved // a child component → useCurrentDocument() // reads whichever provide is nearestOne neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
| Field | Purpose | Example |
|---|---|---|
| __INLINE_CODE_221__ | Logical reference — the same across all translations | __INLINE_CODE_222__ |
| __INLINE_CODE_223__ | Which language this translation represents | __INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__ |
| __INLINE_CODE_228__ | The deployed URL — what users actually navigate to | __INLINE_CODE_229__, __INLINE_CODE_230__ |
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32____INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
| Call | Index state | Result | Why |
|---|---|---|---|
| __INLINE_CODE_269__ | __INLINE_CODE_270__ | __INLINE_CODE_271__ | Exact match |
| __INLINE_CODE_272__ | Same (no DE document exists) | __INLINE_CODE_273__ (or the first available) | Falls back to any available language so the link still works |
| __INLINE_CODE_274__ | __INLINE_CODE_275__ | __INLINE_CODE_276__ | Single-language documents live in __INLINE_CODE_277__; matches across any locale request |
| __INLINE_CODE_278__ | __INLINE_CODE_279__ | __INLINE_CODE_280__ | Locale-specific wins over default when present |
| __INLINE_CODE_281__ | Not in index | __INLINE_CODE_282__ | Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them. |
| __INLINE_CODE_283__ (no lang passed) | — | Uses __INLINE_CODE_284__ from __INLINE_CODE_285__ | The composable's default lang |
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39____INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__ __CODE_BLOCK_41__Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42____INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__For lists:
__CODE_BLOCK_44__Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__ __CODE_BLOCK_47__API
__INLINE_CODE_371__ returns:
| Member | What it does |
|---|---|
| __INLINE_CODE_372__ | async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions. |
| __INLINE_CODE_378__ | sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do. |
| __INLINE_CODE_381__ / __INLINE_CODE_382__ | The same async / sync pair, but for arbitrary list queries (returns the full envelope). |
| __INLINE_CODE_383__ | Drop one entry, or the whole cache when called with no argument. |
| __INLINE_CODE_384__ | The underlying __INLINE_CODE_385__ cache, if you need it. |
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
| __INLINE_CODE_389__ (live href index) | __INLINE_CODE_390__ | |
|---|---|---|
| Freshness | Always-fresh — backed by an SSE subscription | Load-once — re-fetch only on __INLINE_CODE_391__ |
| __INLINE_CODE_392__ | No | Yes |
| Readable from | Components only (it's a composable) | Anywhere — stores, modules, components |
| Best for | Changing feeds, anything edited live | System docs, nav, settings — read often, change rarely |
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__ __CODE_BLOCK_49__- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the __INLINE_CODE_187__. __INLINE_CODE_188__ is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
__CODE_BLOCK_25__ __CODE_BLOCK_26__One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
| Field | Purpose | Example |
|---|---|---|
| __INLINE_CODE_221__ | Logical reference — the same across all translations | __INLINE_CODE_222__ |
| __INLINE_CODE_223__ | Which language this translation represents | __INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__ |
| __INLINE_CODE_228__ | The deployed URL — what users actually navigate to | __INLINE_CODE_229__, __INLINE_CODE_230__ |
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32____INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
| Call | Index state | Result | Why |
|---|---|---|---|
| __INLINE_CODE_269__ | __INLINE_CODE_270__ | __INLINE_CODE_271__ | Exact match |
| __INLINE_CODE_272__ | Same (no DE document exists) | __INLINE_CODE_273__ (or the first available) | Falls back to any available language so the link still works |
| __INLINE_CODE_274__ | __INLINE_CODE_275__ | __INLINE_CODE_276__ | Single-language documents live in __INLINE_CODE_277__; matches across any locale request |
| __INLINE_CODE_278__ | __INLINE_CODE_279__ | __INLINE_CODE_280__ | Locale-specific wins over default when present |
| __INLINE_CODE_281__ | Not in index | __INLINE_CODE_282__ | Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them. |
| __INLINE_CODE_283__ (no lang passed) | — | Uses __INLINE_CODE_284__ from __INLINE_CODE_285__ | The composable's default lang |
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39____INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__ __CODE_BLOCK_41__Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42____INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__For lists:
__CODE_BLOCK_44__Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__ __CODE_BLOCK_47__API
__INLINE_CODE_371__ returns:
| Member | What it does |
|---|---|
| __INLINE_CODE_372__ | async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions. |
| __INLINE_CODE_378__ | sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do. |
| __INLINE_CODE_381__ / __INLINE_CODE_382__ | The same async / sync pair, but for arbitrary list queries (returns the full envelope). |
| __INLINE_CODE_383__ | Drop one entry, or the whole cache when called with no argument. |
| __INLINE_CODE_384__ | The underlying __INLINE_CODE_385__ cache, if you need it. |
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
| __INLINE_CODE_389__ (live href index) | __INLINE_CODE_390__ | |
|---|---|---|
| Freshness | Always-fresh — backed by an SSE subscription | Load-once — re-fetch only on __INLINE_CODE_391__ |
| __INLINE_CODE_392__ | No | Yes |
| Readable from | Components only (it's a composable) | Anywhere — stores, modules, components |
| Best for | Changing feeds, anything edited live | System docs, nav, settings — read often, change rarely |
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__ __CODE_BLOCK_49__- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]:// views/Home.vue — renders the presentation + FAQ videos
import { provideCurrentDocument } from 'mikser-io-sdk-vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const { document } = provideCurrentDocument({ route: () => route.path }) // default expand: ['
The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the __INLINE_CODE_187__. __INLINE_CODE_188__ is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
// composables/document.js
import { useRoute } from 'vue-router'
import { provideCurrentDocument } from 'mikser-io-sdk-vue'
import { client } from '../lib/mikser'
// A view owns its document load; references resolve by default ($ wildcard).
// Pass [] to opt out, or a path list to narrow.
export function provideRouteDocument(expand) {
const route = useRoute()
return provideCurrentDocument({ client, route: () => route.path, expand })
}
// App.vue → provideRouteDocument([]) // neutral: head + plain fields
// views/Home.vue (renders refs) → provideRouteDocument() // default ['
One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createClient } from 'mikser-io-sdk-api'
import { createMikserPlugin, useMikserRoutes } from 'mikser-io-sdk-vue'
import App from './App.vue'
// One client. `data.catalog` pulls the narrow snapshot the data plugin
// writes — fast first paint, CDN-cacheable — then live SSE keeps it
// current.
const documents = createClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
.entities('public', { data: { catalog: 'sitemap', entities: 'page' } })
// Your router. Your routes. Your guards.
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/login', name: 'login', component: () => import('./views/Login.vue') },
{ path: '/dashboard', name: 'dashboard', component: () => import('./views/Dashboard.vue') },
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('./views/NotFound.vue') },
],
})
// Plug mikser into the same router. Await seeded before mount.
const { seeded } = useMikserRoutes(router, {
mapRoute: document => ({
path: document.meta.route,
name: document.id, // required — used as diff key
component: () => import('./views/DocumentPage.vue'),
props: { entityId: document.id },
}),
})
await seeded
createApp(App)
.use(createMikserPlugin({ client: documents }))
.use(router)
.mount('#app')
- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
// build/generate-routes.mjs
import { writeFile } from 'node:fs/promises'
import { createClient } from 'mikser-io-sdk-api'
import { generateMikserRoutes } from 'mikser-io-sdk-vue'
const documents = createClient({ baseUrl: process.env.MIKSER_URL }).entities('public')
const routes = await generateMikserRoutes({
client: documents,
mapRoute: document => ({ path: document.meta.route, name: document.id, /* … */ }),
})
await writeFile('./src/generated/routes.json', JSON.stringify(routes, null, 2))
Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
Field
Purpose
Example
__INLINE_CODE_221__
Logical reference — the same across all translations
__INLINE_CODE_222__
__INLINE_CODE_223__
Which language this translation represents
__INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__
__INLINE_CODE_228__
The deployed URL — what users actually navigate to
__INLINE_CODE_229__, __INLINE_CODE_230__
# documents/en/about.md
---
title: About us
href: /about # logical reference
lang: en # which language
route: /en/about # deployed URL
---
# documents/fr/a-propos.md
---
title: À propos
href: /about # same logical ref — this is the FR translation of "about"
lang: fr
route: /fr/a-propos # localised slug — the URL the browser navigates to
---
# documents/de/uber-uns.md
---
title: Über uns
href: /about # same logical ref
lang: de
route: /de/uber-uns
---
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
<!-- App.vue -->
<script setup>
import { provideHrefIndex } from 'mikser-io-sdk-vue'
provideHrefIndex() // uses the injected client
</script>
<template>
<router-view />
</template>
The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n() // your existing i18n state
const { href } = useHref(locale) // reactive — re-resolves when locale changes
</script>
<template>
<nav>
<router-link :to="href('/')">Home</router-link>
<router-link :to="href('/about')">About</router-link>
<router-link :to="href('/contact')">Contact</router-link>
<!-- Explicit override — show this link in French regardless of current locale -->
<a :href="href('/about', 'fr')">Voir en français</a>
</nav>
</template>
Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
const { href, meta } = useHref()
</script>
<template>
<!-- href(ref) → URL ; meta(ref) → that document's content. Both reactive. -->
<ul class="menu">
<li v-for="item in meta('/menu')?.products" :key="item.id">
<router-link :to="href(item.ref)">{{ item.name }}</router-link>
</li>
</ul>
</template>
__INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
<!-- components/LanguageSwitcher.vue -->
<script setup>
import { useRoute } from 'vue-router'
import { useAlternates } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const route = useRoute()
const { locale, availableLocales } = useI18n()
// Explicit language list — show every locale the app supports, even
// for pages where a translation doesn't exist yet (the SDK falls back
// via href()'s resolution chain). Right shape for a switcher.
const { alternates } = useAlternates({
route: () => route.path,
languages: availableLocales,
})
</script>
<template>
<ul class="lang-switcher">
<li v-for="alt in alternates" :key="alt.lang">
<router-link :to="alt.url">{{ alt.lang.toUpperCase() }}</router-link>
</li>
</ul>
</template>
The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
<script setup>
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
const { href } = useHref(locale)
const crumbs = [
{ ref: '/', label: 'Home' },
{ ref: '/products', label: 'Products' },
{ ref: '/products/sofa-line', label: 'Sofa Line' },
]
</script>
<template>
<nav class="crumbs">
<router-link v-for="c in crumbs" :key="c.ref" :to="href(c.ref)">
{{ c.label }}
</router-link>
</nav>
</template>
The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
<script setup>
import { useDocuments } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
// Query for posts in the current language.
const { documents: posts } = useDocuments(() => ({
filter: { 'meta.collection': 'posts', 'meta.lang': locale.value },
sort: { 'meta.date': -1 },
fields: ['id', 'meta.title', 'meta.route'],
limit: 20,
}))
</script>
<template>
<ul>
<li v-for="post in posts" :key="post.id">
<router-link :to="post.meta.route">{{ post.meta.title }}</router-link>
</li>
</ul>
</template>
Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
import { useRouter } from 'vue-router'
import { useHref } from 'mikser-io-sdk-vue'
import { useI18n } from 'vue-i18n'
const router = useRouter()
const { locale } = useI18n()
const { href } = useHref(locale)
async function onSubmit() {
await saveForm()
router.push(href('/thank-you'))
}
Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
Call
Index state
Result
Why
__INLINE_CODE_269__
__INLINE_CODE_270__
__INLINE_CODE_271__
Exact match
__INLINE_CODE_272__
Same (no DE document exists)
__INLINE_CODE_273__ (or the first available)
Falls back to any available language so the link still works
__INLINE_CODE_274__
__INLINE_CODE_275__
__INLINE_CODE_276__
Single-language documents live in __INLINE_CODE_277__; matches across any locale request
__INLINE_CODE_278__
__INLINE_CODE_279__
__INLINE_CODE_280__
Locale-specific wins over default when present
__INLINE_CODE_281__
Not in index
__INLINE_CODE_282__
Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them.
__INLINE_CODE_283__ (no lang passed)
—
Uses __INLINE_CODE_284__ from __INLINE_CODE_285__
The composable's default lang
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
<!-- AppHead.vue (use with Vue Meta / Unhead / Vue 3's built-in head support) -->
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useHead } from '@unhead/vue'
import { useAlternates } from 'mikser-io-sdk-vue'
const route = useRoute()
// No `languages` arg — return only translations that actually exist.
// Right shape for hreflang: don't advertise pages we don't have.
const { alternates } = useAlternates({ route: () => route.path })
useHead({
link: computed(() => alternates.value.map(({ lang, url }) => ({
rel: 'alternate',
hreflang: lang,
href: url,
}))),
})
</script>
Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
// build/generate-href-index.mjs
import { writeFile } from 'node:fs/promises'
import { createClient } from 'mikser-io-sdk-api'
const documents = createClient({ baseUrl: process.env.MIKSER_URL }).entities('public')
const { items } = await documents.list({
filter: { 'meta.href': { $exists: true } },
fields: ['id', 'meta'],
limit: 10_000,
})
const index = {}
for (const d of items) {
const ref = d.meta?.href, lang = d.meta?.lang ?? 'default'
const url = d.meta?.route ?? d.meta?.destination ?? ref
if (!ref) continue
if (!index[ref]) index[ref] = {}
index[ref][lang] = url
}
await writeFile('./src/generated/href-index.json', JSON.stringify(index, null, 2))
Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
<!-- components/Clip.vue -->
<script setup>
import { useAsset } from 'mikser-io-sdk-vue'
const { url } = useAsset()
</script>
<template>
<video :src="url(clip.meta.url)"
:poster="url(clip.meta.presets.poster)"></video>
</template>
__INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
<!-- App.vue -->
<script setup>
import { provideAssetIndex } from 'mikser-io-sdk-vue'
provideAssetIndex()
</script>
const { asset } = useAsset()
const { url, meta } = asset('/assets/hero.jpg') ?? {}
Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps(['id'])
const { document: article } = useDocument(() => props.id)
const { document: author } = useDocument(() => article.value?.meta.author)
</script>
<template>
<article v-if="article && author">
<h1>{{ article.meta.title }}</h1>
<p>By {{ author.meta.name }}</p>
</article>
</template>
__INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
<script setup>
import { useDocument } from 'mikser-io-sdk-vue'
const props = defineProps(['id'])
// Single call returns article + author + author.organization + hero
// in one round-trip. The initial paint shows fully-resolved data.
const { document: article } = useDocument(() => props.id, {
expand: ['author.organization', 'hero'],
})
</script>
<template>
<article v-if="article">
<h1>{{ article.meta.title }}</h1>
<p>
By {{ article.meta.author.meta.name }}
— {{ article.meta.author.meta.organization.meta.name }}
</p>
<img :src="article.meta.hero.meta.url" :alt="article.meta.hero.meta.alt" />
</article>
</template>
For lists:
<script setup>
import { useDocuments } from 'mikser-io-sdk-vue'
// All articles for /authors/dick, with each article's hero image inlined.
const { documents: articles } = useDocuments(() => ({
filter: { 'meta.component': 'article', 'meta.author': '/authors/dick' },
sort: { 'meta.date': -1 },
expand: ['hero'],
}))
</script>
<template>
<ul>
<li v-for="a in articles" :key="a.id">
<img v-if="a.meta.hero" :src="a.meta.hero.meta.url" :alt="a.meta.hero.meta.alt" />
<h2>{{ a.meta.title }}</h2>
</li>
</ul>
</template>
Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
import { useMikserClient } from 'mikser-io-sdk-vue'
const client = useMikserClient()
const { items } = await client.entities('public').list({
filter: { id: props.id },
expand: ['author.organization', 'hero'],
})
Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
// lib/mikser.js — module scope, no component context needed
import { createReactiveCache } from 'mikser-io-sdk-vue'
import { client } from './client.js'
export const content = createReactiveCache(client.entities('public'))
// a Pinia store
import { defineStore } from 'pinia'
import { content } from '../lib/mikser.js'
export const useSystemStore = defineStore('system', {
actions: {
// async — load + memoize. Use from actions.
loadProducts() {
return content.document('/system/products', { expand: ['products.*.video'] })
},
},
getters: {
// sync + reactive — re-evaluates when the load lands. Use from getters.
translationErrors: () => content.documentSync('/system/translation')?.meta.errors,
},
})
API
__INLINE_CODE_371__ returns:
Member
What it does
__INLINE_CODE_372__
async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions.
__INLINE_CODE_378__
sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do.
__INLINE_CODE_381__ / __INLINE_CODE_382__
The same async / sync pair, but for arbitrary list queries (returns the full envelope).
__INLINE_CODE_383__
Drop one entry, or the whole cache when called with no argument.
__INLINE_CODE_384__
The underlying __INLINE_CODE_385__ cache, if you need it.
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
__INLINE_CODE_389__ (live href index)
__INLINE_CODE_390__
Freshness
Always-fresh — backed by an SSE subscription
Load-once — re-fetch only on __INLINE_CODE_391__
__INLINE_CODE_392__
No
Yes
Readable from
Components only (it's a composable)
Anywhere — stores, modules, components
Best for
Changing feeds, anything edited live
System docs, nav, settings — read often, change rarely
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
// main.js — install the vector plugin alongside the documents one
import { createClient as createVectorClient } from 'mikser-io-sdk-vector'
import { createMikserVectorPlugin } from 'mikser-io-sdk-vue'
const similar = createVectorClient({ baseUrl: import.meta.env.VITE_MIKSER_URL })
app.use(createMikserVectorPlugin({ client: similar }))
<!-- components/SearchBox.vue -->
<script setup>
import { ref } from 'vue'
import { useSimilar } from 'mikser-io-sdk-vue'
const q = ref('')
const { results, loading } = useSimilar('documents', () => q.value, {
limit: 10,
debounce: 200, // ms after the last keystroke before firing
minLength: 2, // skip the request below this length
})
</script>
<template>
<input v-model="q" placeholder="Search…" />
<p v-if="loading">Searching…</p>
<ul>
<li v-for="hit in results" :key="hit.id">
<a :href="hit.id">{{ hit.data?.title }}</a>
<small>distance: {{ hit.distance.toFixed(3) }}</small>
</li>
</ul>
</template>
- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
npm install mikser-io-sdk-vector
The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
type ProductHit = { title: string; sku: string; price: number }
const { results } = useSimilar<ProductHit>('products', () => q.value)
// ↑ results.value[0].data is now ProductHit
What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
import type { ListEnvelope } from 'mikser-io-sdk-api'
interface Article {
id: string
meta: { title: string; date: string; summary?: string }
}
const { documents } = useDocuments<Article>({
filter: { 'meta.collection': 'articles' },
})
// documents.value is Ref<Article[]>; meta.title autocompletes.
The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]
The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the __INLINE_CODE_187__. __INLINE_CODE_188__ is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
__CODE_BLOCK_25__
__CODE_BLOCK_26__
One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__
- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__
Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
Field
Purpose
Example
__INLINE_CODE_221__
Logical reference — the same across all translations
__INLINE_CODE_222__
__INLINE_CODE_223__
Which language this translation represents
__INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__
__INLINE_CODE_228__
The deployed URL — what users actually navigate to
__INLINE_CODE_229__, __INLINE_CODE_230__
__CODE_BLOCK_29__
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__
The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__
Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32__
__INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__
The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__
The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__
Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__
Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
Call
Index state
Result
Why
__INLINE_CODE_269__
__INLINE_CODE_270__
__INLINE_CODE_271__
Exact match
__INLINE_CODE_272__
Same (no DE document exists)
__INLINE_CODE_273__ (or the first available)
Falls back to any available language so the link still works
__INLINE_CODE_274__
__INLINE_CODE_275__
__INLINE_CODE_276__
Single-language documents live in __INLINE_CODE_277__; matches across any locale request
__INLINE_CODE_278__
__INLINE_CODE_279__
__INLINE_CODE_280__
Locale-specific wins over default when present
__INLINE_CODE_281__
Not in index
__INLINE_CODE_282__
Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them.
__INLINE_CODE_283__ (no lang passed)
—
Uses __INLINE_CODE_284__ from __INLINE_CODE_285__
The composable's default lang
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__
Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__
Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39__
__INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__
__CODE_BLOCK_41__
Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42__
__INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__
For lists:
__CODE_BLOCK_44__
Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__
Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__
__CODE_BLOCK_47__
API
__INLINE_CODE_371__ returns:
Member
What it does
__INLINE_CODE_372__
async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions.
__INLINE_CODE_378__
sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do.
__INLINE_CODE_381__ / __INLINE_CODE_382__
The same async / sync pair, but for arbitrary list queries (returns the full envelope).
__INLINE_CODE_383__
Drop one entry, or the whole cache when called with no argument.
__INLINE_CODE_384__
The underlying __INLINE_CODE_385__ cache, if you need it.
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
__INLINE_CODE_389__ (live href index)
__INLINE_CODE_390__
Freshness
Always-fresh — backed by an SSE subscription
Load-once — re-fetch only on __INLINE_CODE_391__
__INLINE_CODE_392__
No
Yes
Readable from
Components only (it's a composable)
Anywhere — stores, modules, components
Best for
Changing feeds, anything edited live
System docs, nav, settings — read often, change rarely
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__
__CODE_BLOCK_49__
- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__
The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__
What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__
The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]: refs resolved
// a child component → useCurrentDocument() // reads whichever provide is nearest
One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
| Field | Purpose | Example |
|---|---|---|
| __INLINE_CODE_221__ | Logical reference — the same across all translations | __INLINE_CODE_222__ |
| __INLINE_CODE_223__ | Which language this translation represents | __INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__ |
| __INLINE_CODE_228__ | The deployed URL — what users actually navigate to | __INLINE_CODE_229__, __INLINE_CODE_230__ |
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32____INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
| Call | Index state | Result | Why |
|---|---|---|---|
| __INLINE_CODE_269__ | __INLINE_CODE_270__ | __INLINE_CODE_271__ | Exact match |
| __INLINE_CODE_272__ | Same (no DE document exists) | __INLINE_CODE_273__ (or the first available) | Falls back to any available language so the link still works |
| __INLINE_CODE_274__ | __INLINE_CODE_275__ | __INLINE_CODE_276__ | Single-language documents live in __INLINE_CODE_277__; matches across any locale request |
| __INLINE_CODE_278__ | __INLINE_CODE_279__ | __INLINE_CODE_280__ | Locale-specific wins over default when present |
| __INLINE_CODE_281__ | Not in index | __INLINE_CODE_282__ | Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them. |
| __INLINE_CODE_283__ (no lang passed) | — | Uses __INLINE_CODE_284__ from __INLINE_CODE_285__ | The composable's default lang |
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39____INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__ __CODE_BLOCK_41__Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42____INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__For lists:
__CODE_BLOCK_44__Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__ __CODE_BLOCK_47__API
__INLINE_CODE_371__ returns:
| Member | What it does |
|---|---|
| __INLINE_CODE_372__ | async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions. |
| __INLINE_CODE_378__ | sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do. |
| __INLINE_CODE_381__ / __INLINE_CODE_382__ | The same async / sync pair, but for arbitrary list queries (returns the full envelope). |
| __INLINE_CODE_383__ | Drop one entry, or the whole cache when called with no argument. |
| __INLINE_CODE_384__ | The underlying __INLINE_CODE_385__ cache, if you need it. |
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
| __INLINE_CODE_389__ (live href index) | __INLINE_CODE_390__ | |
|---|---|---|
| Freshness | Always-fresh — backed by an SSE subscription | Load-once — re-fetch only on __INLINE_CODE_391__ |
| __INLINE_CODE_392__ | No | Yes |
| Readable from | Components only (it's a composable) | Anywhere — stores, modules, components |
| Best for | Changing feeds, anything edited live | System docs, nav, settings — read often, change rarely |
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__ __CODE_BLOCK_49__- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT
]The principle: a document doesn't know its consumers, so the consumer declares what to resolve. Not the document (different views of the same doc want different shapes), not the route table (same problem), not one app-level provide (it resolves for routes that render nothing). The view that renders the references is the one that knows it needs them — so it owns the __INLINE_CODE_187__. __INLINE_CODE_188__ is the default because resolved is the common case; __INLINE_CODE_189__ is the opt-out for the plain-field path.
Rejected on the way here: a single central expand (couples unrelated routes; the head path pays for refs it never reads) and a document- or route-declared expand (the document would have to know who consumes it — it doesn't). Consumer-owned is the one that holds up.
The route-binding wrapper
__INLINE_CODE_190__ is router-agnostic on purpose — it reads a path, it doesn't import vue-router. Bind it once in a small app composable, and every view calls a no-arg helper:
__CODE_BLOCK_25__ __CODE_BLOCK_26__One neutral provide at the root, a per-view override wherever references are rendered, and the consumer — never the document — choosing the resolution. That's the whole architecture.
Router integration
Three helpers, all composition-oriented — your app owns the router, mikser slots in alongside.
__INLINE_CODE_191__
The common case. Wires a live diff loop against an existing vue-router instance: __INLINE_CODE_192__ for catalog entries that appear, __INLINE_CODE_193__ for entries that disappear. Returns __INLINE_CODE_194__.
__CODE_BLOCK_27__- __INLINE_CODE_195__ must return routes with a __INLINE_CODE_196__ — addRoute / removeRoute use it as the diff key. Routes without a name are silently skipped.
- Avoid name collisions between your hand-coded routes and catalog-driven ones; __INLINE_CODE_197__ overwrites on duplicate names.
- __INLINE_CODE_198__ resolves the first time the initial catalog list has landed. Await it before mounting so first-paint navigation hits a registered route instead of falling through to your NotFound and re-navigating once routes appear.
- __INLINE_CODE_199__ stops the live subscription. Idempotent. Also runs automatically on the surrounding effect scope's teardown.
__INLINE_CODE_200__
The build-time version. One-shot list, returns plain route definitions. Use this in a Vite SSG hook or any static-build pipeline that needs to enumerate routes upfront.
__CODE_BLOCK_28__Same __INLINE_CODE_201__ function can power both build-time and runtime — one source of truth for what a content route looks like.
Multilingual __INLINE_CODE_202__ — __INLINE_CODE_203__ + __INLINE_CODE_204__
This is the most load-bearing pattern in the library. If a project has more than one language — and most agency projects do — this is what keeps the codebase from drowning in conditional URL construction.
The pattern, and why it matters
A multilingual content site has two distinct things:
- Logical references — __INLINE_CODE_205__, __INLINE_CODE_206__, __INLINE_CODE_207__ — the abstract idea of a page, independent of language. Stable across translations.
- Deployed URLs — __INLINE_CODE_208__ vs __INLINE_CODE_209__ vs __INLINE_CODE_210__ — the actual paths the server emits, with locale-specific slugs.
Most multilingual setups conflate these. You write __INLINE_CODE_211__ in your template, then hard-code conditionals to swap it. Or you push the language into a vue-router prefix and lose the ability to translate slugs. Or you build a custom routing layer in i18n config that diverges from the actual file structure.
__INLINE_CODE_212__ makes the separation explicit: write the logical reference; the resolver picks the deployed URL based on the current locale and the catalog of published documents. The content author defines the URL for each translation in front-matter; the application code never sees the slug variation.
What this gets you for free:
- Translated slugs (__INLINE_CODE_213__ in DE, __INLINE_CODE_214__ in FR, __INLINE_CODE_215__ in EN) without per-language code paths
- Reactive locale switching — change __INLINE_CODE_216__, every __INLINE_CODE_217__ updates
- Broken-link visibility — __INLINE_CODE_218__ returns __INLINE_CODE_219__ unchanged, so QA sees it in the DOM instead of hitting silent __INLINE_CODE_220__ failures
- Editor-friendly authoring — translators control their own URLs from front-matter, not from code
It's not flashy. It just removes an entire class of bug.
Front-matter convention
Three fields on each translatable document:
| Field | Purpose | Example |
|---|---|---|
| __INLINE_CODE_221__ | Logical reference — the same across all translations | __INLINE_CODE_222__ |
| __INLINE_CODE_223__ | Which language this translation represents | __INLINE_CODE_224__, __INLINE_CODE_225__, __INLINE_CODE_226__, __INLINE_CODE_227__ |
| __INLINE_CODE_228__ | The deployed URL — what users actually navigate to | __INLINE_CODE_229__, __INLINE_CODE_230__ |
Three documents, one logical __INLINE_CODE_231__, three localised slugs. __INLINE_CODE_232__ builds the lookup __INLINE_CODE_233__ from the catalog and keeps it live as content changes.
Documents without __INLINE_CODE_234__ (e.g., single-language pages like __INLINE_CODE_235__) go in a __INLINE_CODE_236__ bucket and are resolvable regardless of the requested locale.
Setup
Once per app, at the root or in a top-level layout component:
__CODE_BLOCK_30__The index is reactive — it stays in sync with the catalog via __INLINE_CODE_237__. Add a new translation in Decap, the URL becomes resolvable within milliseconds.
Basic usage
__CODE_BLOCK_31__Content by reference — __INLINE_CODE_238__ / __INLINE_CODE_239__
The href index already loads every published document keyed by its logical __INLINE_CODE_240__, so it doubles as a content index. __INLINE_CODE_241__ exposes __INLINE_CODE_242__ (and __INLINE_CODE_243__) alongside __INLINE_CODE_244__ — read a known document's fields by its reference without a second query. The natural fit for shared content that's referenced by a stable logical name rather than reached by route: navigation menus, price lists, translation dictionaries, a site-wide settings document.
__CODE_BLOCK_32____INLINE_CODE_245__ returns __INLINE_CODE_246__ for an unknown ref (vs. __INLINE_CODE_247__ which echoes the ref back so broken links stay visible). It reads from the same live subscription as __INLINE_CODE_248__, so it re-evaluates when the referenced document changes — and adds no extra request. Pair it with __INLINE_CODE_249__ to type the returned meta per layout.
When the user toggles __INLINE_CODE_250__, every __INLINE_CODE_251__ binding re-evaluates. No __INLINE_CODE_252__, no manual update.
Common patterns
Language switcher — link to the current page in every other language
The killer use case. Take the current route, find its logical __INLINE_CODE_253__, then resolve to every other language. The reverse lookup + multi-language fan-out is built into the SDK as __INLINE_CODE_254__ — no manual index traversal needed.
__CODE_BLOCK_33__The __INLINE_CODE_255__ ref is reactive on __INLINE_CODE_256__ — navigate to a different page and the list re-resolves automatically. It already excludes the current page's own language (that lives on __INLINE_CODE_257__ from the same composable if you want to include it).
Now a visitor on __INLINE_CODE_258__ clicking "FR" lands on __INLINE_CODE_259__ — the translation, not just a language switcher that takes them back to the home page (which is what most i18n routing libraries do by default).
Breadcrumbs across the section
Same pattern. Logical refs everywhere; the resolver localises:
__CODE_BLOCK_34__The labels would normally come from i18n too (__INLINE_CODE_260__). The point is the URLs — __INLINE_CODE_261__ stays stable; the deployed path resolves per locale.
Path parameters
For collections (blog posts, products, categories) where each item has its own translated slug, store the resolved route directly on the item and skip __INLINE_CODE_262__ for that link — __INLINE_CODE_263__ is for the static references in your code, not for runtime-resolved item URLs.
__CODE_BLOCK_35__Collection items use their own __INLINE_CODE_264__ directly. Use __INLINE_CODE_265__ for the navigation chrome (Home, About, Contact, sections) and for cross-references between specific documents.
Programmatic navigation
__INLINE_CODE_266__ returns a string — it composes with __INLINE_CODE_267__ and any other place a URL is needed:
__CODE_BLOCK_36__Resolution behavior in detail
The fallback chain: requested lang → __INLINE_CODE_268__ bucket → any available language → the input reference unchanged.
| Call | Index state | Result | Why |
|---|---|---|---|
| __INLINE_CODE_269__ | __INLINE_CODE_270__ | __INLINE_CODE_271__ | Exact match |
| __INLINE_CODE_272__ | Same (no DE document exists) | __INLINE_CODE_273__ (or the first available) | Falls back to any available language so the link still works |
| __INLINE_CODE_274__ | __INLINE_CODE_275__ | __INLINE_CODE_276__ | Single-language documents live in __INLINE_CODE_277__; matches across any locale request |
| __INLINE_CODE_278__ | __INLINE_CODE_279__ | __INLINE_CODE_280__ | Locale-specific wins over default when present |
| __INLINE_CODE_281__ | Not in index | __INLINE_CODE_282__ | Unresolved — pass-through. Broken links stay visible in the DOM. QA / Lighthouse / link-checkers spot them. |
| __INLINE_CODE_283__ (no lang passed) | — | Uses __INLINE_CODE_284__ from __INLINE_CODE_285__ | The composable's default lang |
The pass-through behaviour for unresolved refs is deliberate. Silently returning __INLINE_CODE_286__ or __INLINE_CODE_287__ would hide link rot from sight. Passing the input through means broken links render as broken anchors (__INLINE_CODE_288__) that are immediately greppable in the deployed site, surface in link-check tooling, and crash navigation tests instead of silently no-oping.
SEO: hreflang tags
For SEO-critical multilingual sites you'll want __INLINE_CODE_289__ in the document head for every language version of the current page. The href index is exactly the data structure for that:
__CODE_BLOCK_37__Same composable, different shape of the answer — explicit __INLINE_CODE_290__ for switchers (fall back when a translation doesn't exist), no __INLINE_CODE_291__ for SEO (only advertise translations that exist). One catalog, two consumers, no manual index traversal.
How the index gets built
Under the hood __INLINE_CODE_292__ uses __INLINE_CODE_293__ to subscribe to every document with an __INLINE_CODE_294__ field, and recomputes the index whenever the catalog changes. The same SSE stream that powers __INLINE_CODE_295__ / __INLINE_CODE_296__ keeps the href map fresh.
This means:
- Adding a new translation in Decap → the new locale becomes resolvable in the SPA within ~1 second, no rebuild
- Renaming a route in front-matter → all __INLINE_CODE_297__ consumers update on the next render
- Deleting a document → its locale entry drops out of the index; __INLINE_CODE_298__ falls back through the chain
For SSG (scenario B from above), serialise the index at build time and hydrate from it on boot — same shape, just static instead of live:
__CODE_BLOCK_38__Then ship a tiny replacement for __INLINE_CODE_299__ that reads the JSON and provides it statically. The __INLINE_CODE_300__ consumer code doesn't change.
Server-side correspondence
The Vue-side __INLINE_CODE_301__ mirrors what mikser's own __INLINE_CODE_302__ plugin does on the server during template rendering. The naming, the front-matter convention, and the fallback semantics are intentionally identical — same __INLINE_CODE_303__ / __INLINE_CODE_304__ / __INLINE_CODE_305__ fields, same logical-ref-to-deployed-URL resolution.
Practical consequence: if you have a hybrid setup (mikser renders some pages statically, Vue renders others), links between them resolve consistently. A mikser-rendered page that points at __INLINE_CODE_306__ and a Vue-rendered page that calls __INLINE_CODE_307__ produce the same URL for the same locale.
Asset references — __INLINE_CODE_308__
mikser's __INLINE_CODE_309__ plugin is a preset transcoder (video, image, pdf, audio…), not an image pipeline — so the SDK's asset helpers are format-neutral. __INLINE_CODE_310__ returns __INLINE_CODE_311__.
__INLINE_CODE_312__ — the primary helper
Joins a deployed, base-relative served path to the client base. The path is the one mikser's engine stamped into entity meta — __INLINE_CODE_313__ for a served file, __INLINE_CODE_314__ for a transcoded derivative — and surfaced into the catalog via __INLINE_CODE_315__. One rule covers files and derivatives alike. The base is bound automatically from the installed client; nothing is constructed client-side, so there's no __INLINE_CODE_316__ assembly to keep in sync with the plugin.
__INLINE_CODE_317__ is also exported — call it in dev to catch served URLs that resolve to the SPA fallback (a missing base prefix or an unexpanded ref shows up as an __INLINE_CODE_318__/__INLINE_CODE_319__ that fetched HTML instead of media).
__INLINE_CODE_320__ is the routing sibling — call it in dev to catch the other silent class: a navigation that matches no route renders an empty __INLINE_CODE_321__ with no error. Catalog routes name the canonical href (__INLINE_CODE_322__) and live at the localized path (__INLINE_CODE_323__), so reaching for the href as a path is the common miss; the warning resolves the name to the real route (__INLINE_CODE_324__). Pairs with __INLINE_CODE_325__, which fixes the deep-load percent-encoding miss.
__CODE_BLOCK_39____INLINE_CODE_326__ — managed-entity lookup
Resolves a managed asset entity by id to __INLINE_CODE_327__. __INLINE_CODE_328__ is the entity's raw meta block — opaque: mime, dimensions, duration, whatever the preset emitted. This is the lookup that needs __INLINE_CODE_329__ in a parent; without it __INLINE_CODE_330__ returns null.
__CODE_BLOCK_40__ __CODE_BLOCK_41__Image-specific rendering (srcset, __INLINE_CODE_331__ props) is a consumer concern: read __INLINE_CODE_332__ where you actually know the asset is an image, and build whatever element you need from it.
__INLINE_CODE_333__ is only needed for __INLINE_CODE_334__ entity lookups. __INLINE_CODE_335__ works without it.
References & inline expansion
Reference fields written as __INLINE_CODE_336__ (per ADR-0007) arrive on the wire as bare href strings — __INLINE_CODE_337__. Two ways to turn them into entity objects, picked by whether you want the deep data to stay live.
Chained __INLINE_CODE_338__ — each level stays live independently. Updates to the author propagate without re-fetching the article. Best when both layers update independently.
__CODE_BLOCK_42____INLINE_CODE_339__ on __INLINE_CODE_340__ / __INLINE_CODE_341__ — the initial snapshot arrives with refs already resolved to entity objects. Best when you'd otherwise pay N round-trips on first paint (a multi-hop chain like __INLINE_CODE_342__, or arrays of refs like __INLINE_CODE_343__).
__CODE_BLOCK_43__For lists:
__CODE_BLOCK_44__Path forms: dot-notation walks expanded entities (__INLINE_CODE_344__); __INLINE_CODE_345__ iterates __INLINE_CODE_346__-keyed arrays (__INLINE_CODE_347__); both canonical (__INLINE_CODE_348__) and normalized (__INLINE_CODE_349__) segments are accepted.
Server caps default to __INLINE_CODE_350__, __INLINE_CODE_351__, __INLINE_CODE_352__ per request — configurable via __INLINE_CODE_353__ in __INLINE_CODE_354__. Exceeding any cap surfaces as a __INLINE_CODE_355__ with __INLINE_CODE_356__ on the underlying call.
SSE deltas stay expanded. Both the initial snapshot AND every forward update emit fully-expanded entities. The api plugin's subscribe handler registers an engine-level __INLINE_CODE_357__ against the subscription's filter + expand; mutations to any entity within the expansion graph (the root, the author, the author's organization, …) trigger a re-emit with the freshly-resolved tree. Reactive consumers see consistent expanded data across the lifetime of the subscription.
For ad-hoc one-shot reads that don't need to participate in the SSE subscription at all — sitemap builders, SSG enumeration, AI agent calls — drop to the underlying __INLINE_CODE_358__ client:
__CODE_BLOCK_45__Missing targets or cycles silently leave the ref as a string at the deepest position — same convention as the underlying api, per ADR-0007 B6.
Reactive content cache — __INLINE_CODE_359__
The composables above (__INLINE_CODE_360__, __INLINE_CODE_361__ / __INLINE_CODE_362__) live inside a component's inject context — call them in __INLINE_CODE_363__, never in a Pinia store action or a plain module. __INLINE_CODE_364__ is the escape hatch: a plain factory, not a composable. It works anywhere — Pinia store actions and getters, plain __INLINE_CODE_365__ modules, anywhere outside a component — because it carries its own state instead of reaching for __INLINE_CODE_366__. Create it once and share the instance.
It's a thin Vue shell around __INLINE_CODE_367__'s framework-agnostic __INLINE_CODE_368__. The win Vue adds is the sync, reactive read: __INLINE_CODE_369__ returns the loaded doc immediately and re-evaluates when the load lands — it bridges the core cache's __INLINE_CODE_370__ to a Vue reactivity tick. That's the half a composable can't give you from a Pinia getter.
__CODE_BLOCK_46__ __CODE_BLOCK_47__API
__INLINE_CODE_371__ returns:
| Member | What it does |
|---|---|
| __INLINE_CODE_372__ | async — loads + memoizes the doc at that logical __INLINE_CODE_373__, resolves to the doc (__INLINE_CODE_374__) or __INLINE_CODE_375__. References resolve by default (__INLINE_CODE_376__ wildcard); pass __INLINE_CODE_377__ to opt out or a path list to narrow. Use from store actions. |
| __INLINE_CODE_378__ | sync + reactive — returns the loaded doc or __INLINE_CODE_379__, and re-evaluates when the load lands. Same default expand as __INLINE_CODE_380__. Use from Pinia getters / sync template helpers — this is the half composables can't do. |
| __INLINE_CODE_381__ / __INLINE_CODE_382__ | The same async / sync pair, but for arbitrary list queries (returns the full envelope). |
| __INLINE_CODE_383__ | Drop one entry, or the whole cache when called with no argument. |
| __INLINE_CODE_384__ | The underlying __INLINE_CODE_385__ cache, if you need it. |
The cache key folds in __INLINE_CODE_386__ — a doc fetched with expand is a different shape from the same doc without, so they're distinct entries.
When to reach for it vs. the live href index
It pairs with the live href index (__INLINE_CODE_387__ / __INLINE_CODE_388__), it doesn't replace it:
| __INLINE_CODE_389__ (live href index) | __INLINE_CODE_390__ | |
|---|---|---|
| Freshness | Always-fresh — backed by an SSE subscription | Load-once — re-fetch only on __INLINE_CODE_391__ |
| __INLINE_CODE_392__ | No | Yes |
| Readable from | Components only (it's a composable) | Anywhere — stores, modules, components |
| Best for | Changing feeds, anything edited live | System docs, nav, settings — read often, change rarely |
Reach for __INLINE_CODE_393__ / __INLINE_CODE_394__ when the content changes under you and the UI must track it. Reach for __INLINE_CODE_395__ for the load-once, expand-capable, read-from-anywhere content that backs a store.
Semantic search — __INLINE_CODE_396__ + __INLINE_CODE_397__
Bridges the __INLINE_CODE_398__ client into Vue. Same provide/inject shape as the documents plugin; the search composable handles debounce + stale-result discard so a fast-typing user doesn't see older results clobber newer ones.
__CODE_BLOCK_48__ __CODE_BLOCK_49__- __INLINE_CODE_399__ is reactive; updates push to __INLINE_CODE_400__ in place.
- __INLINE_CODE_401__ is true only while a request is in flight, not during the debounce wait. Use it to show a spinner, not a placeholder.
- __INLINE_CODE_402__ is populated when __INLINE_CODE_403__ rejects. Branch on it to render a fallback.
- __INLINE_CODE_404__ forces a fresh request against the current query. Use after the vector store has been updated server-side.
__INLINE_CODE_405__ is an optional runtime dependency — the framework SDK doesn't import it. Install it only if you use semantic search:
__CODE_BLOCK_50__The hit shape is generic on the embedded payload — type the __INLINE_CODE_406__ field for typed templates:
__CODE_BLOCK_51__What this library doesn't include
Deliberately. Each of these is a per-project decision:
- A __INLINE_CODE_407__ component. Every project renders content differently (markdown engine, layouts, sanitization). __INLINE_CODE_408__ gives you the data; you write the component.
- A markdown renderer. Use __INLINE_CODE_409__, __INLINE_CODE_410__, or whatever your project standardizes on. If you render Markdown server-side via the __INLINE_CODE_411__ plugin, __INLINE_CODE_412__ is already HTML.
- A layout switcher. Some teams use __INLINE_CODE_413__; some use named layouts; some pick via __INLINE_CODE_414__. Stay neutral.
- Auth / token UI. Tokens are configuration — __INLINE_CODE_415__ handles them.
TypeScript
Full types ship with the package. Most generics let you narrow the document shape:
__CODE_BLOCK_52__The router-related types are forwarded from __INLINE_CODE_416__ so __INLINE_CODE_417__ returns __INLINE_CODE_418__ directly.
License
MIT