| 1 | ---
|
| 2 | title: Revalidation Optimization
|
| 3 | ---
|
| 4 |
|
| 5 | # Revalidation Optimization
|
| 6 |
|
| 7 | [MODES: framework, data]
|
| 8 |
|
| 9 | <br/>
|
| 10 | <br/>
|
| 11 |
|
| 12 | After a mutation or some navigations, React Router re-runs loaders so
|
| 13 | the UI stays in sync with the server. That default is the right
|
| 14 | starting point. When a loader is expensive, or a mutation cannot
|
| 15 | affect that route's data, you can skip the reload.
|
| 16 |
|
| 17 | <docs-warning>
|
| 18 | Skipping revalidation can leave the UI out of sync with the server.
|
| 19 | Prefer targeting a specific action or navigation, and fall back to
|
| 20 | `defaultShouldRevalidate` instead of always returning `false`.
|
| 21 | </docs-warning>
|
| 22 |
|
| 23 | ## Default behavior
|
| 24 |
|
| 25 | The default behavior differs between Framework and Data Modes:
|
| 26 |
|
| 27 | - **Framework Mode with SSR**
|
| 28 | - Defaults to opt-out behavior - active loaders are revalidated on navigations and successful submissions ([`Link`][link], [`Form`][form], [`fetcher.submit`](fetcher-submit))
|
| 29 | - Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
|
| 30 | - **Framework "SPA Mode" and Data Mode**
|
| 31 | - Defaults to opt-out behavior on successful submissions - active loaders are revalidated on successful submissions ([`Form`][form], [`fetcher.submit`])
|
| 32 | - Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
|
| 33 | - 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
|
| 34 | - A GET navigation to the _exact_ same URL is treated like a page refresh and all loaders are revalidated.
|
| 35 |
|
| 36 | Matched matched routes are handled independently - A child that skips
|
| 37 | revalidation does not skip any ancestor routes.
|
| 38 |
|
| 39 | [`fetcher.load`][use-fetcher] only revalidates by default after action
|
| 40 | submissions and explicit [`useRevalidator`][use-revalidator] calls, not
|
| 41 | on search-param or param-driven navigations.
|
| 42 |
|
| 43 | A plain `fetch()` to a [resource route][resource-routes] does not
|
| 44 | go through the router, so it does not revalidate loaders.
|
| 45 |
|
| 46 | ## Skip a route with `shouldRevalidate`
|
| 47 |
|
| 48 | Export `shouldRevalidate` from the [route module][route-module]
|
| 49 | (Framework Mode) or set it on the [route object][data-mode]
|
| 50 | (Data Mode). Returning `false` skips **that route's** loader.
|
| 51 |
|
| 52 | ```tsx filename=app/routes/dashboard.tsx
|
| 53 | // Framework Mode
|
| 54 | export function shouldRevalidate() {
|
| 55 | return false;
|
| 56 | }
|
| 57 | ```
|
| 58 |
|
| 59 | ```tsx src/main.tsx
|
| 60 | // Data Mode
|
| 61 | createBrowserRouter([
|
| 62 | {
|
| 63 | path: "/dashboard",
|
| 64 | loader: dashboardLoader,
|
| 65 | shouldRevalidate: () => false,
|
| 66 | Component: Dashboard,
|
| 67 | },
|
| 68 | ]);
|
| 69 | ```
|
| 70 |
|
| 71 | Always returning `false` opts that route out of the default
|
| 72 | behavior completely, including cases you usually still want
|
| 73 | (param changes, explicit [`useRevalidator`][use-revalidator]).
|
| 74 | Prefer the conditional form below.
|
| 75 |
|
| 76 | ## Opt out of specific requests
|
| 77 |
|
| 78 | Inspect
|
| 79 | [`ShouldRevalidateFunctionArgs`][should-revalidate-args]
|
| 80 | and return `defaultShouldRevalidate` for everything else.
|
| 81 |
|
| 82 | ```tsx
|
| 83 | import type { ShouldRevalidateFunctionArgs } from "react-router";
|
| 84 |
|
| 85 | export function shouldRevalidate({
|
| 86 | formMethod,
|
| 87 | formAction,
|
| 88 | defaultShouldRevalidate,
|
| 89 | }: ShouldRevalidateFunctionArgs) {
|
| 90 | if (
|
| 91 | formMethod === "POST" &&
|
| 92 | formAction?.endsWith("/analytics")
|
| 93 | ) {
|
| 94 | return false;
|
| 95 | }
|
| 96 |
|
| 97 | return defaultShouldRevalidate;
|
| 98 | }
|
| 99 | ```
|
| 100 |
|
| 101 | Other useful fields:
|
| 102 |
|
| 103 | - `formData`, `json`, `text` — the submission body
|
| 104 | - `actionResult`, `actionStatus` — the action's return value
|
| 105 | - `currentUrl`, `nextUrl`, `currentParams`, `nextParams` —
|
| 106 | the navigation
|
| 107 |
|
| 108 | You can ignore search-param-only updates while still
|
| 109 | revalidating when the pathname changes:
|
| 110 |
|
| 111 | ```tsx
|
| 112 | export function shouldRevalidate({
|
| 113 | currentUrl,
|
| 114 | nextUrl,
|
| 115 | defaultShouldRevalidate,
|
| 116 | }: ShouldRevalidateFunctionArgs) {
|
| 117 | if (currentUrl.pathname === nextUrl.pathname) {
|
| 118 | return false;
|
| 119 | }
|
| 120 |
|
| 121 | return defaultShouldRevalidate;
|
| 122 | }
|
| 123 | ```
|
| 124 |
|
| 125 | ## Skip revalidation for one event
|
| 126 |
|
| 127 | Pass `defaultShouldRevalidate={false}` at the call site so you
|
| 128 | do not have to change every route file. This works on
|
| 129 | [`<Form>`][form], [`<Link>`][link], `<fetcher.Form>`, and as an
|
| 130 | option to [`useSubmit`][use-submit], `fetcher.submit`,
|
| 131 | [`useNavigate`][use-navigate], and
|
| 132 | [`useSearchParams`][use-search-params].
|
| 133 |
|
| 134 | ```tsx
|
| 135 | import { Form, Link } from "react-router";
|
| 136 |
|
| 137 | <Link
|
| 138 | to="/search?q=shoes"
|
| 139 | defaultShouldRevalidate={false}
|
| 140 | >
|
| 141 | Search Shoes
|
| 142 | </Link>
|
| 143 |
|
| 144 | <Form
|
| 145 | method="post"
|
| 146 | action="/analytics"
|
| 147 | defaultShouldRevalidate={false}
|
| 148 | >
|
| 149 | <button>Track Click</button>
|
| 150 | </Form>
|
| 151 | ```
|
| 152 |
|
| 153 | ```tsx
|
| 154 | fetcher.submit(
|
| 155 | { intent: "save-progress" },
|
| 156 | {
|
| 157 | method: "post",
|
| 158 | action: "/save-progress",
|
| 159 | defaultShouldRevalidate: false,
|
| 160 | },
|
| 161 | );
|
| 162 | ```
|
| 163 |
|
| 164 | If a matched route does **not** export `shouldRevalidate`, this
|
| 165 | value is used directly for that loader. If it **does** export
|
| 166 | `shouldRevalidate`, the value is passed in as
|
| 167 | `defaultShouldRevalidate` and the route still has the final say.
|
| 168 |
|
| 169 | That is why a child `shouldRevalidate` that always returns
|
| 170 | `false` cannot hide a root reload after `fetcher.submit`. Either
|
| 171 | also opt `root` out for that case, or pass
|
| 172 | `defaultShouldRevalidate: false` at the call site when `root`
|
| 173 | has no `shouldRevalidate` of its own.
|
| 174 |
|
| 175 | [data-mode]: ../start/data/route-object#shouldrevalidate
|
| 176 | [form]: ../api/components/Form
|
| 177 | [link]: ../api/components/Link
|
| 178 | [resource-routes]: ./resource-routes
|
| 179 | [route-module]: ../start/framework/route-module#shouldrevalidate
|
| 180 | [should-revalidate-args]: https://api.reactrouter.com/v8/interfaces/react-router.ShouldRevalidateFunctionArgs.html
|
| 181 | [use-fetcher]: ../api/hooks/useFetcher
|
| 182 | [use-navigate]: ../api/hooks/useNavigate
|
| 183 | [use-revalidator]: ../api/hooks/useRevalidator
|
| 184 | [use-search-params]: ../api/hooks/useSearchParams
|
| 185 | [use-submit]: ../api/hooks/useSubmit
|