Skip to content

vitepress-plugin-cross-site-router

Fixes navigation and prefetch between several independently deployed VitePress instances that share a single origin.

Problem

When several VitePress sites are hosted on one domain under different base paths (for example /alt-server/, /alt-workstation/), two problems appear:

  1. SPA navigation error - VitePress intercepts every click on same-origin links and tries a SPA transition, cannot find the neighbor site's chunks and breaks.
  2. Wasted prefetch requests - the VitePress prefetcher tries to preload pages of neighboring sites and gets 404.

Solution

The plugin rewires the router and prefetch logic:

  1. Intercepts router.onBeforeRouteChange: if the href does not belong to the current base, it performs a location.href (full navigation).
  2. Wraps window.IntersectionObserver before VitePress creates its prefetch observer, so links to neighboring sites never enter the prefetch queue.

Installation

bash
npm install @ampernic/vitepress-plugin-cross-site-router

Usage

The plugin has two parts: a Vite plugin (optional, reserved for future use) and a client runtime.

VitePress config

ts
import { CrossSiteRouterPlugin } from '@ampernic/vitepress-plugin-cross-site-router'

export default defineConfig({
  vite: {
    plugins: [CrossSiteRouterPlugin()],
  },
})

Theme enhanceApp

ts
import {
  installCrossSiteRouter,
  suppressCrossSitePrefetch,
} from '@ampernic/vitepress-plugin-cross-site-router/client'

export default {
  enhanceApp({ router, siteData }) {
    installCrossSiteRouter(router, siteData)
    suppressCrossSitePrefetch(siteData)
  },
}

API

installCrossSiteRouter(router, siteData, isCrossSite?)

Installs a router.onBeforeRouteChange hook and chains any previously installed hook. When the target href does not start with siteData.base or is absent from __VP_HASH_MAP__ (in prod), it uses location.href instead of a SPA transition. The optional third argument isCrossSite: (href, base) => boolean | undefined overrides the detection logic (returning undefined falls back to the default predicate).

suppressCrossSitePrefetch(siteData, isCrossSite?)

Wraps window.IntersectionObserver so the VitePress prefetch observer never queues links leading outside siteData.base. The third argument is the same as for installCrossSiteRouter, which makes it convenient to pass one predicate to both functions.

isCrossSiteHref(href, base)

Utility that checks whether an href is cross-site relative to the given base. In prod it consults __VP_HASH_MAP__: pages that share the base prefix but are missing from the current build are also treated as cross-site (relevant to Forgejo Pages, where several unpacked instances share a parent path).

CrossSiteRouterPlugin(options?)

A Vite plugin. Currently an empty marker ({ name: 'vitepress-plugin-cross-site-router' }), the whole logic lives on the client. Reserved as an entry point for future build-time integration.

OptionTypeDescription
isCrossSite(href, base) => boolean | undefinedType of the cross-site detection predicate. The plugin does not apply it yet, pass it as the third argument to installCrossSiteRouter / suppressCrossSitePrefetch.