SSR, SPA & routing

bankai-vue components behave the same in a client-only Vue app (SPA) and under Nuxt server rendering (SSR) or static generation (SSG). This page covers what makes that work — the hydration model, how BankaiLink finds your router, and the one config value you should set when you server-render.

Environments

  • SPA (Vite). Install the plugin with createBankai() and import components where you use them. Everything runs on the client.
  • Nuxt SSR / SSG. The Nuxt module auto-registers every component and installs the config per app via a generated plugin — so under SSR each request gets its own reactive config, with no cross-request leakage.

Hydration model

Components are SSR-safe by design: they generate hydration-stable ids (Vue's useId) and read no window or document during setup, so the server HTML and the client's first render always agree.

Any state that can only be known on the client is deliberately withheld until after hydration, then updates reactively. The clearest example is BankaiLink's data-bankai-external flag: on the server and the first client render it is computed from linkOrigin (or an origin-less fallback); the accurate window.location-based host check only kicks in once the component has mounted. That is why the flag never causes a hydration mismatch.

Router detection

BankaiLink renders the right element for internal (to) navigation with no wiring, resolved in this order:

  1. an explicit linkComponent override in BankaiConfig, if you set one;
  2. a globally-registered NuxtLink (preferred under Nuxt — it adds prefetch, base-URL, and external-URL handling);
  3. a globally-registered RouterLink (a plain vue-router app);
  4. otherwise a native <a> — no router installed.

Detection reads the app's global component registry, which is exactly where vue-router and Nuxt register these — so core never imports a router and stays dependency-free. A string to with no router degrades to a plain <a href>.

<template>
<!-- Internal: renders NuxtLink / RouterLink if a router is installed, else a plain <a href="/about">. -->
<BankaiLink to="/about">About</BankaiLink>

<!-- External: always a native <a>, marked data-bankai-external, with rel="noopener noreferrer". -->
<BankaiLink href="https://example.com" target="_blank">Docs</BankaiLink>
</template>

vue-router types (opt-in)

By default to is a router-agnostic type. If you use vue-router, add the types entry to your tsconfig.json to type it as vue-router's RouteLocationRaw. It is an opt-in augmentation, so a router-free app stays dependency-free.

// tsconfig.json — type BankaiLink's `to` as vue-router's RouteLocationRaw
{
"compilerOptions": {
"types": ["@bankai-vue/core/vue-router"]
}
}

Set linkOrigin when you server-render

To decide a link is external, BankaiLink compares its href host against a reference origin. On the server there is no window — and under SSG there is no request at all — so the current origin is not knowable at render time.

Set linkOrigin to your canonical site origin so the check is accurate and identical on server and client. A client-only SPA can leave it unset — it falls back to window.location after hydration; with no origin available at all, any absolute http(s) URL is treated as external.

import { createBankai } from '@bankai-vue/core';

app.use(
createBankai({
// Your canonical site origin — makes the external-host check accurate and
// identical on server and client (hydration-safe) under SSR/SSG.
linkOrigin: 'https://example.com',
}),
);
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@bankai-vue/nuxt'],
css: ['@bankai-vue/theme-bankai'],

bankai: {
config: { linkOrigin: 'https://example.com' },
},
});

See also

Getting started for install and configuration, and BankaiLink for its full props and reflected state.