Revalidation Optimization
Revalidation Optimization
Framework Data
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.
Warning
Skipping revalidation can leave the UI out of sync with the server.
Prefer targeting a specific action or navigation, and fall back to
defaultShouldRevalidate instead of always returning false.
Default behavior
The default behavior differs between Framework and Data Modes:
- Framework Mode with SSR
- Defaults to opt-out behavior - active loaders are revalidated on navigations and successful submissions (
Link,Form,fetcher.submit) - Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
- Defaults to opt-out behavior - active loaders are revalidated on navigations and successful submissions (
- Framework "SPA Mode" and Data Mode
- Defaults to opt-out behavior on successful submissions - active loaders are revalidated on successful submissions (
Form,fetcher.submit)- Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
- Defaults to opt-in behavior for GET navigations (
Link) - active loaders are only revalidated if their dynamic params changed, or if any search params changed- A GET navigation to the exact same URL is treated like a page refresh and all loaders are revalidated.
- Defaults to opt-out behavior on successful submissions - active loaders are revalidated on successful submissions (
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.
Skip a route with shouldRevalidate
Export 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.
Opt out of specific requests
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 navigation
You 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;
}Skip revalidation for one event
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.