@k8ordo/router

Serve under a base path

Sometimes an app is served below a path of its origin, such as https://example.com/docs/, rather than at its root. The route table stays written from the app’s root even then, and the router adds and removes that base path.

On this page

Set Vite’s base

The base path is Vite’s base, and the router reads it from import.meta.env.BASE_URL.

vite.config.ts
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

export default defineConfig({
  base: '/docs/',
  plugins: [react()],
});

The route table does not change. The /products page is still /products in the table, and /docs/products in the browser’s address bar.

href and navigateTo put the base path in front of the URL they build.

ts
href('/products'); // '/docs/products'
href('/'); // '/docs/'

This is why href returns a string rather than a path in the table: what it returns is a URL, base path included.

Information

Note

The same holds under @k8ordo/static and @k8ordo/server. A URL handed to a Server Action’s redirect() gets its base path when href builds it, too.

Warning

Pitfall

Do not hand what href returned to @k8ordo/state’s href. It adds the base path as well, which doubles it into /docs/docs/products. Hand it a path in the table’s terms, such as /products.

Paths you read leave the base path out

usePathname returns the path without the base path: at /docs/products, it is /products.

What comes back therefore compares directly with the route table’s patterns. useMatch checks the path without the base path too, and <Router> matches the table against the path below it.

A URL outside the base path is not the app’s, so <Router> leaves it to the browser whatever the route table says.

Add and remove it in your own code

withBase and withoutBase are the router’s own two steps, for code of your own.

ts
withBase('/products'); // '/docs/products'
withoutBase('/docs/products'); // '/products'
withoutBase('/docs'); // '/'
withoutBase('/elsewhere'); // null

withoutBase returns null for a path outside the base path. The base path itself, /docs, becomes / with or without its trailing slash.

Code that Vite does not process has no import.meta.env, so it passes the base path as the second argument.

ts
withBase('/products', '/docs/'); // '/docs/products'

A relative base such as ./ names no path, so neither function adds or removes anything.

k8ordo

React libraries that use Baseline features without holding back.

© 2026 k8o — MIT License

Typeset in Noto Sans JP & M PLUS 2