After a mutation succeeds, there is no way to re-render server-rendered content. The only
option is window.location.reload() — a full document load, losing scroll, focus and every
client signal on the page.
Why invalidate() does not cover it
useQuery(...).invalidate() (packages/stx/src/signals.ts:1292) re-runs a client-side JSON
fetch. That works when the list came from JSON. It cannot re-run a <script server> block,
which is where an SSR-first app puts its data:
<script server>
const projects = await db.unsafe('SELECT … WHERE owner_id = $1', [user.id])
</script>Delete a project via useMutation, and nothing can make that query run again.
The router's public API has no equivalent — packages/router/src/client.ts:1362-1372:
var router = { navigate, navigateTo, prefetch, clearCache, cache, swap, updateNav }navigate(currentUrl) early-returns on the same URL (client.ts:509), and fragment
navigation swaps the whole <main> (client.ts:931) rather than a named region.
What that costs
Four mutations in a real app end with window.location.reload() for exactly this reason —
settings.stx:326, :460, :572, :617. Each one is a successful in-app action that
drops the user to a cold page load.
What peers provide
| framework | API | granularity |
|---|---|---|
| Inertia | router.reload({ only: ['projects'] }) | named props |
| Nuxt | refreshNuxtData() / refresh() from useAsyncData | per-key |
| Remix | automatic loader revalidation after an action | route loaders |
| Livewire | component re-render on action | component |
The common shape is "re-run the server data for this view, patch the DOM, keep client state". stx has the transport for it already — the fragment path fetches a rendered fragment and swaps it.
Suggested direction
The cheapest useful version is a router method that re-fetches the CURRENT url through the existing fragment path and swaps, bypassing the same-URL early return:
router.refresh() // re-fetch current url, swap <main>, keep scrollThat alone replaces all four location.reload() calls. The early-return at client.ts:509
already has a force parameter (client.ts:481) — this is close to exposing what exists.
A more precise version worth considering separately is a named region —
<div data-stx-refresh="projects"> plus router.refresh('projects') — so a mutation can
re-render a list without disturbing the rest of the page. That is the Inertia only:
model and it is what makes the pattern feel instant rather than merely correct.
Related: navigate() cannot express same-URL re-navigation either. The router supports it
via a third force argument, but navigate() forwards only { replace }
(signals.ts:1062) and StxNavigateOptions has no force. An app that writes a
preference cookie and needs the same URL re-rendered has no expressible way to ask.