Repository navigation
@rangojs/router 0.32.0
Fixed: a prefetch held by a revalidate() that read the source page no longer shows another page's layout (#1333)
Before. A layout whose revalidate() compares the page you leave with
the page you go to (currentParams, currentUrl, fromRouteName), such as
a template that shows a category hero on category pages, could show the
wrong copy of itself. A prefetch of /cart sent from a product page kept
the layout, which is right for the product page. The prefetch is shared by
every page, so a click on the same link from a category page took the same
answer and kept the category page's layout: the cart rendered under the
category hero, and stayed there on the next navigation. Production's
"viewport" default made this likely; development, which prefetches
nothing by default, did not show it.
Now. When a revalidate() reads the page navigated away from and keeps
the segment, the response is stored for the page that prefetched it
(x-rsc-prefetch-scope: source), as an intercept target's already is. A
click from another page prefetches its own answer, or asks the server. A
revalidate() that re-renders, or that reads only the destination
(nextParams, nextUrl, isAction(), toRouteName), keeps one shared
prefetch.
A prerendered target is covered too: its stored entry runs a held layout's
revalidate() only when the params change, so on equal params the kept
layout scopes the prefetch the same way. So does a prerendered page kept on
a search-only change (/item/1?tab=a to /item/1?tab=b): a click from
/item/2 no longer shows item 2.
No change in your code. prefetchKey=":source" stays for a response that
differs by source in ways the server cannot see; the router now scopes the
revalidate()-driven case itself.
Fixed: rango generate --runtime resolves through plugins that set up in configResolved (#1332)
Before. rango generate --runtime forwarded the app's resolver plugins
straight from the unresolved Vite config, so their config and
configResolved hooks never ran. A resolver that builds its state there,
such as vite-tsconfig-paths, matched nothing, and discovery failed with
Cannot find module '~routes/shop'. A plugin with apply: "build" was
forwarded into the serve-mode discovery too.
Now. --runtime resolves the Vite config as vite dev would, without
rango() and rsc(), and forwards the resolver plugins from the result. An
alias added by a plugin's config() hook resolves, and apply: "build"
plugins, string or function form, are left out. define and
resolve.conditions set from a plugin's config hook are still not picked
up (docs/manifests.md).
Added: location state on document responses (#1334)
Before. ctx.setLocationState() and redirect(url, { state }) reached
history.state only through client navigations and actions. A document
response dropped the state: a <form action={fn}> posted without JS (or
before hydration), a payment provider redirecting the shopper back to a
handler, a link from an email.
Now. A document response carries it:
- a page load or no-JS form post that renders a page carries the state in
its bootstrap payload; the client writes it into the entry when it starts,
anduseLocationStatereturns it once the page has hydrated (as it does
for stored state); - a document request that redirects carries the state in a one-shot cookie
(<stateCookieName>-ls,__Host-on https,HttpOnly,SameSite=Lax,
60 seconds) that the target's document request reads and clears, so a
refresh does not show a flash again. A prefetch orfetch()of the target
(Sec-Fetch-Destother than a browsing context) leaves it to the
navigation. The cookie holds JSON: give state
that crosses a document redirect plain JSON data. State over 4000 bytes,
or that JSON cannot encode, is dropped with a warning; - such a document, and the redirect carrying the cookie, are sent
Cache-Control: private, no-store; the document cache neither stores nor
serves them. Apprroute keeps its shell: state set by middleware or the
cookie rides the live payload of a HIT or MISS (no shell holds it), and a
route whose handler sets state is not captured and renders in full.
// Works the same for a client action, a no-JS form post and a request from
// outside the app.
path("/checkout/callback", async (ctx) =>
redirect("/orders/confirmed", { state: [Flash({ text: "Paid" })] }),
);If a handler or middleware calls setLocationState on every request, it is
now delivered on document loads too; a clearOnReload slot it sets applies
on the load it arrived with. On a ppr route, set state from middleware: a
shell HIT runs no handler. Without JS the state reaches no reader (only the
client applies it), and a no-store page is kept out of the browser's
back/forward cache. A document redirect from one router to a path another
router on the same origin serves loses its state: each router reads only its
own cookie.
Docs: one location-state definition keeps only its last write (#1334)
Entries accumulate across definitions, but for one definition the last write
wins: two setLocationState(Toasts(...)) calls in one response, or
redirect(url, { state: [Toasts(a), Toasts(b)] }), deliver only the later
value. The setLocationState, createLocationState and redirect JSDoc and
the /hooks state.md skill now say so. Write an array once to combine
messages.