After a mutation or some navigations, React Router re-runs loaders so the UI stays in sync with the server. That default is the right starting point. When a loader is expensive, or a mutation cannot affect that route's data, you can skip the reload.
The default behavior differs between Framework and Data Modes:
Link, Form, fetcher.submit)Form, [fetcher.submit])
Link) - active loaders are only revalidated if their dynamic params changed, or if any search params changed
Matched matched routes are handled independently - A child that skips revalidation does not skip any ancestor routes.
fetcher.load only revalidates by default after action
submissions and explicit useRevalidator calls, not
on search-param or param-driven navigations.
A plain fetch() to a resource route does not
go through the router, so it does not revalidate loaders.
shouldRevalidateExport shouldRevalidate from the route module
(Framework Mode) or set it on the route object
(Data Mode). Returning false skips that route's loader.
// Framework Mode
export function shouldRevalidate() {
return false;
}
// Data Mode
createBrowserRouter([
{
path: "/dashboard",
loader: dashboardLoader,
shouldRevalidate: () => false,
Component: Dashboard,
},
]);
Always returning false opts that route out of the default
behavior completely, including cases you usually still want
(param changes, explicit useRevalidator).
Prefer the conditional form below.
Inspect
ShouldRevalidateFunctionArgs
and return defaultShouldRevalidate for everything else.
import type { ShouldRevalidateFunctionArgs } from "react-router";
export function shouldRevalidate({
formMethod,
formAction,
defaultShouldRevalidate,
}: ShouldRevalidateFunctionArgs) {
if (
formMethod === "POST" &&
formAction?.endsWith("/analytics")
) {
return false;
}
return defaultShouldRevalidate;
}
Other useful fields:
formData, json, text — the submission bodyactionResult, actionStatus — the action's return valuecurrentUrl, nextUrl, currentParams, nextParams —
the navigationYou can ignore search-param-only updates while still revalidating when the pathname changes:
export function shouldRevalidate({
currentUrl,
nextUrl,
defaultShouldRevalidate,
}: ShouldRevalidateFunctionArgs) {
if (currentUrl.pathname === nextUrl.pathname) {
return false;
}
return defaultShouldRevalidate;
}
Pass defaultShouldRevalidate={false} at the call site so you
do not have to change every route file. This works on
<Form>, <Link>, <fetcher.Form>, and as an
option to useSubmit, fetcher.submit,
useNavigate, and
useSearchParams.
import { Form, Link } from "react-router";
<Link
to="/search?q=shoes"
defaultShouldRevalidate={false}
>
Search Shoes
</Link>
<Form
method="post"
action="/analytics"
defaultShouldRevalidate={false}
>
<button>Track Click</button>
</Form>
fetcher.submit(
{ intent: "save-progress" },
{
method: "post",
action: "/save-progress",
defaultShouldRevalidate: false,
},
);
If a matched route does not export shouldRevalidate, this
value is used directly for that loader. If it does export
shouldRevalidate, the value is passed in as
defaultShouldRevalidate and the route still has the final say.
That is why a child shouldRevalidate that always returns
false cannot hide a root reload after fetcher.submit. Either
also opt root out for that case, or pass
defaultShouldRevalidate: false at the call site when root
has no shouldRevalidate of its own.