--- title: Revalidation Optimization --- # Revalidation Optimization [MODES: 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. 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`][link], [`Form`][form], [`fetcher.submit`](fetcher-submit)) - Failed submissions returning a 4xx/5xx status do not trigger revalidations by default - **Framework "SPA Mode" and Data Mode** - Defaults to opt-out behavior on successful submissions - active loaders are revalidated on successful submissions ([`Form`][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`][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. Matched matched routes are handled independently - A child that skips revalidation does not skip any ancestor routes. [`fetcher.load`][use-fetcher] only revalidates by default after action submissions and explicit [`useRevalidator`][use-revalidator] calls, not on search-param or param-driven navigations. A plain `fetch()` to a [resource route][resource-routes] does not go through the router, so it does not revalidate loaders. ## Skip a route with `shouldRevalidate` Export `shouldRevalidate` from the [route module][route-module] (Framework Mode) or set it on the [route object][data-mode] (Data Mode). Returning `false` skips **that route's** loader. ```tsx filename=app/routes/dashboard.tsx // Framework Mode export function shouldRevalidate() { return false; } ``` ```tsx src/main.tsx // 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`][use-revalidator]). Prefer the conditional form below. ## Opt out of specific requests Inspect [`ShouldRevalidateFunctionArgs`][should-revalidate-args] and return `defaultShouldRevalidate` for everything else. ```tsx 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 body - `actionResult`, `actionStatus` — the action's return value - `currentUrl`, `nextUrl`, `currentParams`, `nextParams` — the navigation You can ignore search-param-only updates while still revalidating when the pathname changes: ```tsx 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], ``, and as an option to [`useSubmit`][use-submit], `fetcher.submit`, [`useNavigate`][use-navigate], and [`useSearchParams`][use-search-params]. ```tsx import { Form, Link } from "react-router"; Search Shoes ``` ```tsx 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. [data-mode]: ../start/data/route-object#shouldrevalidate [form]: ../api/components/Form [link]: ../api/components/Link [resource-routes]: ./resource-routes [route-module]: ../start/framework/route-module#shouldrevalidate [should-revalidate-args]: https://api.reactrouter.com/v8/interfaces/react-router.ShouldRevalidateFunctionArgs.html [use-fetcher]: ../api/hooks/useFetcher [use-navigate]: ../api/hooks/useNavigate [use-revalidator]: ../api/hooks/useRevalidator [use-search-params]: ../api/hooks/useSearchParams [use-submit]: ../api/hooks/useSubmit