@open-agent-kit/bridge 1.2 requires every App-specific route — user views, admin views, and any custom API routes — to live under a /app/{id}/ prefix, so multiple Apps can run side by side without their routes colliding. A fixed set of contract routes that SAALT calls directly (/meta, /tools, /knowledge/*, /cron) stay unprefixed.
This guide walks through migrating an existing App that still serves routes at the root (e.g. /user, /api/progress) to the prefixed convention.
Starting a new App from the App starter? It already ships with path-based routing — you can skip this guide.
Step 1: Update the Bridge package
Code
This release also made agentId optional when constructing the Bridge for standalone Apps. If you're on an older Bridge version, see Bridge for the (separate, optional) call-site changes.
Step 2: Move your routes under the prefix
In app/routes.ts, wrap your App's routes with its id (the same id your /meta route returns) using the prefix helper:
Code
SAALT forwards the full /app/my-app/… path to your App — it does not strip the prefix, so your routes must be physically defined at that full path.
Step 3: Fix every in-app reference
This is the step that's easiest to do incompletely, and the bugs it leaves are subtle: routes.ts and obvious <Link>s get the prefix, but useFetcher action paths, plain <a href> tags, and relative paths get missed. A missed reference resolves against the current URL and 404s only when that one link or button is used — so it can pass a quick smoke test and still ship broken.
Search for every reference and convert the ones that target your App's own routes:
Code
| Reference | Must become |
|---|---|
route(...) in routes.ts | /app/my-app/user/..., /app/my-app/api/... |
navigate("...") | /app/my-app/user/... |
redirect("...") (loader/action) | /app/my-app/user/... |
<Link to> / <NavLink to> | /app/my-app/user/... |
useFetcher().submit(_, { action }) | /app/my-app/api/... |
<Form action> / <fetcher.Form action> | /app/my-app/api/... |
fetch("/api/...") | /app/my-app/api/... |
plain <a href> | /app/my-app/user/... (see below) |
Two rules apply to every row above:
- Use the full absolute path. Every in-app reference must start with
/app/my-app/…. - Convert relative paths too — not just ones already starting with
/. There is no router basename (see Step 4), so a relative path is never auto-prefixed; it resolves against the current URL and breaks. A relativeto="user"oraction: "api/progress"is the more dangerous case because it looks deliberate.
Plain <a href> is a special case. <Link> and navigate compute the path in JavaScript and ignore the document's <base href>. A plain <a href> does not — the browser resolves it against <base href={basePath}> (Step 4) and triggers a full-page reload instead of client-side navigation. Prefer converting in-app plain anchors to <Link> with an absolute path. Keep a plain <a> only for links that intentionally leave your App — the SAALT shell chat, mailto:, or external URLs.
Step 4: Don't set a router basename — render <base href> instead
A router basename would also prefix /meta and /tools, which must stay unprefixed. So instead:
- Keep every route defined with the full
/app/my-app/…prefix (Step 2). - Write every in-app navigation as an absolute
/app/my-app/…path (Step 3). - Render
<base href>once, in your root layout, using thesaalt_base_pathheader SAALT forwards on every request (value/app/my-app). This is only for the browser to resolve plain<a href>and static assets — it does not affect<Link>/navigate, which are pure JavaScript.
Code
Step 5: Update your Vite config
Set base to your App's prefix so built asset URLs resolve correctly under the proxy:
Code
Step 6: Rename legacy headers
If your App still reads the pre-rename header names, update them:
| Old | New |
|---|---|
oak_session_token | saalt_session_token |
oak_server_url | saalt_server_url |
oak_base_path | saalt_base_path |
Check middleware, route handlers, and any helper that extracts these values from the request.
Next steps
- Architecture — the routes SAALT calls once your App is registered.
- Bridge — the SDK your App uses to talk back to SAALT.
- Local development — run and register your App locally to test the migration.