Middleware
Rewrite the path SSGOI matches on, so a locale or tenant prefix does not need its own rules.
Add middleware when the URL carries structure your transition rules should ignore — a locale, a tenant prefix, a rewrite. It rewrites the route ids once, before anything else sees them.
Normalize before matching
Middleware takes the raw from and to ids and returns the logical ids every rule works with.
raw URL /en/posts/42 → /en/posts
│ middleware removes locale
logical route /posts/42 → /posts
│
└─ one shared rule setconst config = {
middleware: (from, to) => ({
from: from.replace(/^\/(en|ko)(?=\/|$)/, ""),
to: to.replace(/^\/(en|ko)(?=\/|$)/, ""),
}),
transitions: [
{
on: "/posts/**",
except: "/posts",
transition: drill(),
},
],
};One identity all the way through
The resolved id is what rules match and what scroll positions are filed under.
Above, /en/posts and /ko/posts both become /posts. They match the same rules and share one restored scroll position. If the two locales must keep separate state, do not collapse them onto the same id.
For scroll bookkeeping the middleware is called with the same path on both sides, and only its
from result is used. Write it as a plain per-path rewrite: one that branches on from !== to, or rewrites the two sides differently, will file scroll under an id you did not expect.What not to use it for
Middleware removes incidental URL structure. It should not hide a complicated transition model.
- Device branching — use
transitions({ isMobile }). - Persistent layout lifetime — use boundaries and scoped keys.
- Effect variants — pass props to
drill(),sheet()and the rest.