47 KiB
@remix-run/router
1.19.2
Patch Changes
- Update the
unstable_dataStrategy
API to allow for more advanced implementations (#11943)- Rename
unstable_HandlerResult
tounstable_DataStrategyResult
- The return signature has changed from a parallel array of
unstable_DataStrategyResult[]
(parallel tomatches
) to a key/value object ofrouteId => unstable_DataStrategyResult
- This allows you to more easily decide to opt-into or out-of revalidating data that may not have been revalidated by default (via
match.shouldLoad
) - ⚠️ This is a breaking change if you've currently adopted
unstable_dataStrategy
- This allows you to more easily decide to opt-into or out-of revalidating data that may not have been revalidated by default (via
- Added a new
fetcherKey
parameter tounstable_dataStrategy
to allow differentiation from navigational and fetcher calls - You should now return/throw a result from your
handlerOverride
instead of returning aDataStrategyResult
- If you are aggregating the results of
match.resolve()
into a final results object you should not need to think about theDataStrategyResult
type - If you are manually filling your results object from within your
handlerOverride
, then you will need to assign aDataStrategyResult
as the value so React Router knows if it's a successful execution or an error.
- If you are aggregating the results of
- Rename
- Preserve view transition through redirects (#11925)
- Fix blocker usage when
blocker.proceed
is called quickly/synchronously (#11930) - Preserve pending view transitions through a router revalidation call (#11917)
1.19.1
Patch Changes
-
Fog of War: Update
unstable_patchRoutesOnMiss
logic so that we call the method when we match routes with dynamic param or splat segments in case there exists a higher-scoring static route that we've not yet discovered. (#11883)- We also now leverage an internal FIFO queue of previous paths we've already called
unstable_patchRouteOnMiss
against so that we don't re-call on subsequent navigations to the same path
- We also now leverage an internal FIFO queue of previous paths we've already called
-
Rename
unstable_patchRoutesOnMiss
tounstable_patchRoutesOnNavigation
to match new behavior (#11888)
1.19.0
Minor Changes
- Add a new
replace(url, init?)
alternative toredirect(url, init?)
that performs ahistory.replaceState
instead of ahistory.pushState
on client-side navigation redirects (#11811) - Add a new
unstable_data()
API for usage with Remix Single Fetch (#11836)- This API is not intended for direct usage in React Router SPA applications
- It is primarily intended for usage with
createStaticHandler.query()
to allow loaders/actions to return arbitrary data +status
/headers
without forcing the serialization of data into aResponse
instance - This allows for more advanced serialization tactics via
unstable_dataStrategy
such as serializing viaturbo-stream
in Remix Single Fetch - ⚠️ This removes the
status
field fromHandlerResult
- If you need to return a specific
status
fromunstable_dataStrategy
you should instead do so viaunstable_data()
- If you need to return a specific
Patch Changes
- Fix internal cleanup of interrupted fetchers to avoid invalid revalidations on navigations (#11839)
- When a
fetcher.load
is interrupted by anaction
submission, we track it internally and force revalidation once theaction
completes - We previously only cleared out this internal tracking info on a successful navigation submission
- Therefore, if the
fetcher.load
was interrupted by afetcher.submit
, then we wouldn't remove it from this internal tracking info on successful load (incorrectly) - And then on the next navigation it's presence in the internal tracking would automatically trigger execution of the
fetcher.load
again, ignoring anyshouldRevalidate
logic - This fix cleans up the internal tracking so it applies to both navigation submission and fetcher submissions
- When a
- Fix initial hydration behavior when using
future.v7_partialHydration
along withunstable_patchRoutesOnMiss
(#11838)- During initial hydration,
router.state.matches
will now include any partial matches so that we can render ancestorHydrateFallback
components
- During initial hydration,
1.18.0
Minor Changes
- Stabilize
future.unstable_skipActionErrorRevalidation
asfuture.v7_skipActionErrorRevalidation
(#11769)- When this flag is enabled, actions will not automatically trigger a revalidation if they return/throw a
Response
with a4xx
/5xx
status code - You may still opt-into revalidation via
shouldRevalidate
- This also changes
shouldRevalidate
'sunstable_actionStatus
parameter toactionStatus
- When this flag is enabled, actions will not automatically trigger a revalidation if they return/throw a
Patch Changes
- Fix bubbling of errors thrown from
unstable_patchRoutesOnMiss
(#11786) - Fix hydration in SSR apps using
unstable_patchRoutesOnMiss
that matched a splat route on the server (#11790)
1.17.1
Patch Changes
- Fog of War (unstable): Trigger a new
router.routes
identity/reflow during route patching (#11740) - Fog of War (unstable): Fix initial matching when a splat route matches (#11759)
1.17.0
Minor Changes
-
Add support for Lazy Route Discovery (a.k.a. Fog of War) (#11626)
- RFC: https://github.com/remix-run/react-router/discussions/11113
unstable_patchRoutesOnMiss
docs: https://reactrouter.com/en/main/routers/create-browser-router
1.16.1
Patch Changes
- Support
unstable_dataStrategy
onstaticHandler.queryRoute
(#11515)
1.16.0
Minor Changes
- Add a new
unstable_dataStrategy
configuration option (#11098)- This option allows Data Router applications to take control over the approach for executing route loaders and actions
- The default implementation is today's behavior, to fetch all loaders in parallel, but this option allows users to implement more advanced data flows including Remix single-fetch, middleware/context APIs, automatic loader caching, and more
- Move
unstable_dataStrategy
fromcreateStaticHandler
tostaticHandler.query
so it can be request-specific for use with theResponseStub
approach in Remix. It's not really applicable toqueryRoute
for now since that's a singular handler call anyway so any pre-processing/post/processing could be done there manually. (#11377) - Add a new
future.unstable_skipActionRevalidation
future flag (#11098)- Currently, active loaders revalidate after any action, regardless of the result
- With this flag enabled, actions that return/throw a 4xx/5xx response status will no longer automatically revalidate
- This should reduce load on your server since it's rare that a 4xx/5xx should actually mutate any data
- If you need to revalidate after a 4xx/5xx result with this flag enabled, you can still do that via returning
true
fromshouldRevalidate
shouldRevalidate
now also receives a newunstable_actionStatus
argument alongsideactionResult
so you can make decision based on the status of theaction
response without having to encode it into the action data
- Added a
skipLoaderErrorBubbling
flag tostaticHandler.query
to disable error bubbling on loader executions for single-fetch scenarios where the client-side router will handle the bubbling (#11098)
1.15.3
Patch Changes
- Fix a
future.v7_partialHydration
bug that would re-run loaders below the boundary on hydration if SSR loader errors bubbled to a parent boundary (#11324) - Fix a
future.v7_partialHydration
bug that would consider the router uninitialized if a route did not have a loader (#11325)
1.15.2
Patch Changes
- Preserve hydrated errors during partial hydration runs (#11305)
1.15.1
Patch Changes
- Fix encoding/decoding issues with pre-encoded dynamic parameter values (#11199)
1.15.0
Minor Changes
-
Add a
createStaticHandler
future.v7_throwAbortReason
flag to throwrequest.signal.reason
(defaults to aDOMException
) when a request is aborted instead of anError
such asnew Error("query() call aborted: GET /path")
(#11104)- Please note that
DOMException
was added in Node v17 so you will not get aDOMException
on Node 16 and below.
- Please note that
Patch Changes
- Respect the
ErrorResponse
status code if passed togetStaticContextFormError
(#11213)
1.14.2
Patch Changes
- Fix bug where dashes were not picked up in dynamic parameter names (#11160)
- Do not attempt to deserialize empty JSON responses (#11164)
1.14.1
Patch Changes
- Fix bug with
route.lazy
not working correctly on initial SPA load whenv7_partialHydration
is specified (#11121) - Fix bug preventing revalidation from occurring for persisted fetchers unmounted during the
submitting
phase (#11102) - De-dup relative path logic in
resolveTo
(#11097)
1.14.0
Minor Changes
-
Added a new
future.v7_partialHydration
future flag that enables partial hydration of a data router when Server-Side Rendering. This allows you to providehydrationData.loaderData
that has values for some initially matched route loaders, but not all. When this flag is enabled, the router will callloader
functions for routes that do not have hydration loader data duringrouter.initialize()
, and it will render down to the deepest providedHydrateFallback
(up to the first route without hydration data) while it executes the unhydrated routes. (#11033)For example, the following router has a
root
andindex
route, but only providedhydrationData.loaderData
for theroot
route. Because theindex
route has aloader
, we need to run that during initialization. Withfuture.v7_partialHydration
specified,<RouterProvider>
will render theRootComponent
(because it has data) and then theIndexFallback
(since it does not have data). OnceindexLoader
finishes, application will update and displayIndexComponent
.let router = createBrowserRouter( [ { id: "root", path: "/", loader: rootLoader, Component: RootComponent, Fallback: RootFallback, children: [ { id: "index", index: true, loader: indexLoader, Component: IndexComponent, HydrateFallback: IndexFallback, }, ], }, ], { future: { v7_partialHydration: true, }, hydrationData: { loaderData: { root: { message: "Hydrated from Root!" }, }, }, } );
If the above example did not have an
IndexFallback
, thenRouterProvider
would instead render theRootFallback
while it executed theindexLoader
.Note: When
future.v7_partialHydration
is provided, the<RouterProvider fallbackElement>
prop is ignored since you can move it to aFallback
on your top-most route. ThefallbackElement
prop will be removed in React Router v7 whenv7_partialHydration
behavior becomes the standard behavior. -
Add a new
future.v7_relativeSplatPath
flag to implement a breaking bug fix to relative routing when inside a splat route. (#11087)This fix was originally added in #10983 and was later reverted in #11078 because it was determined that a large number of existing applications were relying on the buggy behavior (see #11052)
The Bug The buggy behavior is that without this flag, the default behavior when resolving relative paths is to ignore any splat (
*
) portion of the current route path.The Background This decision was originally made thinking that it would make the concept of nested different sections of your apps in
<Routes>
easier if relative routing would replace the current splat:<BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="dashboard/*" element={<Dashboard />} /> </Routes> </BrowserRouter>
Any paths like
/dashboard
,/dashboard/team
,/dashboard/projects
will match theDashboard
route. The dashboard component itself can then render nested<Routes>
:function Dashboard() { return ( <div> <h2>Dashboard</h2> <nav> <Link to="/">Dashboard Home</Link> <Link to="team">Team</Link> <Link to="projects">Projects</Link> </nav> <Routes> <Route path="/" element={<DashboardHome />} /> <Route path="team" element={<DashboardTeam />} /> <Route path="projects" element={<DashboardProjects />} /> </Routes> </div> ); }
Now, all links and route paths are relative to the router above them. This makes code splitting and compartmentalizing your app really easy. You could render the
Dashboard
as its own independent app, or embed it into your large app without making any changes to it.The Problem
The problem is that this concept of ignoring part of a path breaks a lot of other assumptions in React Router - namely that
"."
always means the current location pathname for that route. When we ignore the splat portion, we start getting invalid paths when using"."
:// If we are on URL /dashboard/team, and we want to link to /dashboard/team: function DashboardTeam() { // ❌ This is broken and results in <a href="/dashboard"> return <Link to=".">A broken link to the Current URL</Link>; // ✅ This is fixed but super unintuitive since we're already at /dashboard/team! return <Link to="./team">A broken link to the Current URL</Link>; }
We've also introduced an issue that we can no longer move our
DashboardTeam
component around our route hierarchy easily - since it behaves differently if we're underneath a non-splat route, such as/dashboard/:widget
. Now, our"."
links will, properly point to ourself inclusive of the dynamic param value so behavior will break from it's corresponding usage in a/dashboard/*
route.Even worse, consider a nested splat route configuration:
<BrowserRouter> <Routes> <Route path="dashboard"> <Route path="*" element={<Dashboard />} /> </Route> </Routes> </BrowserRouter>
Now, a
<Link to=".">
and a<Link to="..">
inside theDashboard
component go to the same place! That is definitely not correct!Another common issue arose in Data Routers (and Remix) where any
<Form>
should post to it's own routeaction
if you the user doesn't specify a form action:let router = createBrowserRouter({ path: "/dashboard", children: [ { path: "*", action: dashboardAction, Component() { // ❌ This form is broken! It throws a 405 error when it submits because // it tries to submit to /dashboard (without the splat value) and the parent // `/dashboard` route doesn't have an action return <Form method="post">...</Form>; }, }, ], });
This is just a compounded issue from the above because the default location for a
Form
to submit to is itself ("."
) - and if we ignore the splat portion, that now resolves to the parent route.The Solution If you are leveraging this behavior, it's recommended to enable the future flag, move your splat to it's own route, and leverage
../
for any links to "sibling" pages:<BrowserRouter> <Routes> <Route path="dashboard"> <Route index path="*" element={<Dashboard />} /> </Route> </Routes> </BrowserRouter> function Dashboard() { return ( <div> <h2>Dashboard</h2> <nav> <Link to="..">Dashboard Home</Link> <Link to="../team">Team</Link> <Link to="../projects">Projects</Link> </nav> <Routes> <Route path="/" element={<DashboardHome />} /> <Route path="team" element={<DashboardTeam />} /> <Route path="projects" element={<DashboardProjects />} /> </Router> </div> ); }
This way,
.
means "the full current pathname for my route" in all cases (including static, dynamic, and splat routes) and..
always means "my parents pathname".
Patch Changes
- Catch and bubble errors thrown when trying to unwrap responses from
loader
/action
functions (#11061) - Fix
relative="path"
issue when renderingLink
/NavLink
outside of matched routes (#11062)
1.13.1
Patch Changes
- Revert the
useResolvedPath
fix for splat routes due to a large number of applications that were relying on the buggy behavior (see https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329). We plan to re-introduce this fix behind a future flag in the next minor version. (#11078)
1.13.0
Minor Changes
- Export the
PathParam
type from the public API (#10719)
Patch Changes
- Fix bug with
resolveTo
in splat routes (#11045)- This is a follow up to #10983 to handle the few other code paths using
getPathContributingMatches
- This removes the
UNSAFE_getPathContributingMatches
export from@remix-run/router
since we no longer need this in thereact-router
/react-router-dom
layers
- This is a follow up to #10983 to handle the few other code paths using
- Do not revalidate unmounted fetchers when
v7_fetcherPersist
is enabled (#11044)
1.12.0
Minor Changes
- Add
unstable_flushSync
option torouter.navigate
androuter.fetch
to tell the React Router layer to opt-out ofReact.startTransition
and intoReactDOM.flushSync
for state updates (#11005)
Patch Changes
-
Fix
relative="path"
bug where relative path calculations started from the full location pathname, instead of from the current contextual route pathname. (#11006)<Route path="/a"> <Route path="/b" element={<Component />}> <Route path="/c" /> </Route> </Route>; function Component() { return ( <> {/* This is now correctly relative to /a/b, not /a/b/c */} <Link to=".." relative="path" /> <Outlet /> </> ); }
1.11.0
Minor Changes
-
Add a new
future.v7_fetcherPersist
flag to the@remix-run/router
to change the persistence behavior of fetchers whenrouter.deleteFetcher
is called. Instead of being immediately cleaned up, fetchers will persist until they return to anidle
state (RFC) (#10962)- This is sort of a long-standing bug fix as the
useFetchers()
API was always supposed to only reflect in-flight fetcher information for pending/optimistic UI -- it was not intended to reflect fetcher data or hang onto fetchers after they returned to anidle
state - Keep an eye out for the following specific behavioral changes when opting into this flag and check your app for compatibility:
- Fetchers that complete while still mounted will no longer appear in
useFetchers()
. They served effectively no purpose in there since you can access the data viauseFetcher().data
). - Fetchers that previously unmounted while in-flight will not be immediately aborted and will instead be cleaned up once they return to an
idle
state. They will remain exposed viauseFetchers
while in-flight so you can still access pending/optimistic data after unmount.
- Fetchers that complete while still mounted will no longer appear in
- This is sort of a long-standing bug fix as the
-
When
v7_fetcherPersist
is enabled, the router now performs ref-counting on fetcher keys viagetFetcher
/deleteFetcher
so it knows when a given fetcher is totally unmounted from the UI (#10977)- Once a fetcher has been totally unmounted, we can ignore post-processing of a persisted fetcher result such as a redirect or an error
- The router will also pass a new
deletedFetchers
array to the subscriber callbacks so that the UI layer can remove associated fetcher data
-
Add support for optional path segments in
matchPath
(#10768)
Patch Changes
- Fix
router.getFetcher
/router.deleteFetcher
type definitions which incorrectly specifiedkey
as an optional parameter (#10960)
1.10.0
Minor Changes
- Add experimental support for the View Transitions API by allowing users to opt-into view transitions on navigations via the new
unstable_viewTransition
option torouter.navigate
(#10916)
Patch Changes
- Allow 404 detection to leverage root route error boundary if path contains a URL segment (#10852)
- Fix
ErrorResponse
type to avoid leaking internal field (#10876)
1.9.0
Minor Changes
- In order to move towards stricter TypeScript support in the future, we're aiming to replace current usages of
any
withunknown
on exposed typings for user-provided data. To do this in Remix v2 without introducing breaking changes in React Router v6, we have added generics to a number of shared types. These continue to default toany
in React Router and are overridden withunknown
in Remix. In React Router v7 we plan to move these tounknown
as a breaking change. (#10843)Location
now accepts a generic for thelocation.state
valueActionFunctionArgs
/ActionFunction
/LoaderFunctionArgs
/LoaderFunction
now accept a generic for thecontext
parameter (only used in SSR usages viacreateStaticHandler
)- The return type of
useMatches
(now exported asUIMatch
) accepts generics formatch.data
andmatch.handle
- both of which were already set tounknown
- Move the
@private
class exportErrorResponse
to anUNSAFE_ErrorResponseImpl
export since it is an implementation detail and there should be no construction ofErrorResponse
instances in userland. This frees us up to export atype ErrorResponse
which correlates to an instance of the class viaInstanceType
. Userland code should only ever be usingErrorResponse
as a type and should be type-narrowing viaisRouteErrorResponse
. (#10811) - Export
ShouldRevalidateFunctionArgs
interface (#10797) - Removed private/internal APIs only required for the Remix v1 backwards compatibility layer and no longer needed in Remix v2 (
_isFetchActionRedirect
,_hasFetcherDoneAnything
) (#10715)
Patch Changes
- Add method/url to error message on aborted
query
/queryRoute
calls (#10793) - Fix a race-condition with loader/action-thrown errors on
route.lazy
routes (#10778) - Fix type for
actionResult
on the arguments object passed toshouldRevalidate
(#10779)
1.8.0
Minor Changes
- Add's a new
redirectDocument()
function which allows users to specify that a redirect from aloader
/action
should trigger a document reload (viawindow.location
) instead of attempting to navigate to the redirected location via React Router (#10705)
Patch Changes
- Fix an issue in
queryRoute
that was not always identifying thrownResponse
instances (#10717) - Ensure hash history always includes a leading slash on hash pathnames (#10753)
1.7.2
Patch Changes
- Trigger an error if a
defer
promise resolves/rejects withundefined
in order to match the behavior of loaders and actions which must return a value ornull
(#10690) - Properly handle fetcher redirects interrupted by normal navigations (#10674, #10709)
- Initial-load fetchers should not automatically revalidate on GET navigations (#10688)
- Enhance the return type of
Route.lazy
to prohibit returning an empty object (#10634)
1.7.1
Patch Changes
- Fix issues with reused blockers on subsequent navigations (#10656)
1.7.0
Minor Changes
-
Add support for
application/json
andtext/plain
encodings forrouter.navigate
/router.fetch
submissions. To leverage these encodings, pass your data in abody
parameter and specify the desiredformEncType
: (#10413)// By default, the encoding is "application/x-www-form-urlencoded" router.navigate("/", { formMethod: "post", body: { key: "value" }, }); async function action({ request }) { // await request.formData() => FormData instance with entry [key=value] }
// Pass `formEncType` to opt-into a different encoding (json) router.navigate("/", { formMethod: "post", formEncType: "application/json", body: { key: "value" }, }); async function action({ request }) { // await request.json() => { key: "value" } }
// Pass `formEncType` to opt-into a different encoding (text) router.navigate("/", { formMethod: "post", formEncType: "text/plain", body: "Text submission", }); async function action({ request }) { // await request.text() => "Text submission" }
Patch Changes
- Call
window.history.pushState/replaceState
before updating React Router state (instead of after) so thatwindow.location
matchesuseLocation
during synchronous React 17 rendering (#10448)- ⚠️ However, generally apps should not be relying on
window.location
and should always referenceuseLocation
when possible, aswindow.location
will not be in sync 100% of the time (due topopstate
events, concurrent mode, etc.)
- ⚠️ However, generally apps should not be relying on
- Strip
basename
from thelocation
provided to<ScrollRestoration getKey>
to match theuseLocation
behavior (#10550) - Avoid calling
shouldRevalidate
for fetchers that have not yet completed a data load (#10623) - Fix
unstable_useBlocker
key issues inStrictMode
(#10573) - Upgrade
typescript
to 5.1 (#10581)
1.6.3
Patch Changes
- Allow fetcher revalidations to complete if submitting fetcher is deleted (#10535)
- Re-throw
DOMException
(DataCloneError
) when attempting to perform aPUSH
navigation with non-serializable state. (#10427) - Ensure revalidations happen when hash is present (#10516)
- upgrade jest and jsdom (#10453)
1.6.2
Patch Changes
- Fix HMR-driven error boundaries by properly reconstructing new routes and
manifest
in\_internalSetRoutes
(#10437) - Fix bug where initial data load would not kick off when hash is present (#10493)
1.6.1
Patch Changes
- Fix
basename
handling when navigating without a path (#10433) - "Same hash" navigations no longer re-run loaders to match browser behavior (i.e.
/path#hash -> /path#hash
) (#10408)
1.6.0
Minor Changes
-
Enable relative routing in the
@remix-run/router
when providing a source route ID from which the path is relative to: (#10336)- Example:
router.navigate("../path", { fromRouteId: "some-route" })
. - This also applies to
router.fetch
which already receives a source route ID
- Example:
-
Introduce a new
@remix-run/router
future.v7_prependBasename
flag to enablebasename
prefixing to all paths coming intorouter.navigate
androuter.fetch
.- Previously the
basename
was prepended in the React Router layer, but now that relative routing is being handled by the router we need prepend thebasename
after resolving any relative paths - This also enables
basename
support inuseFetcher
as well
- Previously the
Patch Changes
- Enhance
LoaderFunction
/ActionFunction
return type to preventundefined
from being a valid return value (#10267) - Ensure proper 404 error on
fetcher.load
call to a route without aloader
(#10345) - Deprecate the
createRouter
detectErrorBoundary
option in favor of the newmapRouteProperties
option for converting a framework-agnostic route to a framework-aware route. This allows us to set more than just thehasErrorBoundary
property during route pre-processing, and is now used for mappingComponent -> element
andErrorBoundary -> errorElement
inreact-router
. (#10287) - Fixed a bug where fetchers were incorrectly attempting to revalidate on search params changes or routing to the same URL (using the same logic for route
loader
revalidations). However, since fetchers have a static href, they should only revalidate onaction
submissions orrouter.revalidate
calls. (#10344) - Decouple
AbortController
usage between revalidating fetchers and the thing that triggered them such that the unmount/deletion of a revalidating fetcher doesn't impact the ongoing triggering navigation/revalidation (#10271)
1.5.0
Minor Changes
-
Added support for Future Flags in React Router. The first flag being introduced is
future.v7_normalizeFormMethod
which will normalize the exposeduseNavigation()/useFetcher()
formMethod
fields as uppercase HTTP methods to align with thefetch()
behavior. (#10207)- When
future.v7_normalizeFormMethod === false
(default v6 behavior),useNavigation().formMethod
is lowercaseuseFetcher().formMethod
is lowercase
- When
future.v7_normalizeFormMethod === true
:useNavigation().formMethod
is uppercaseuseFetcher().formMethod
is uppercase
- When
Patch Changes
- Provide fetcher submission to
shouldRevalidate
if the fetcher action redirects (#10208) - Properly handle
lazy()
errors during router initialization (#10201) - Remove
instanceof
check forDeferredData
to be resilient to ESM/CJS boundaries in SSR bundling scenarios (#10247) - Update to latest
@remix-run/web-fetch@4.3.3
(#10216)
1.4.0
Minor Changes
-
Introducing Lazy Route Modules! (#10045)
In order to keep your application bundles small and support code-splitting of your routes, we've introduced a new
lazy()
route property. This is an async function that resolves the non-route-matching portions of your route definition (loader
,action
,element
/Component
,errorElement
/ErrorBoundary
,shouldRevalidate
,handle
).Lazy routes are resolved on initial load and during the
loading
orsubmitting
phase of a navigation or fetcher call. You cannot lazily define route-matching properties (path
,index
,children
) since we only execute your lazy route functions after we've matched known routes.Your
lazy
functions will typically return the result of a dynamic import.// In this example, we assume most folks land on the homepage so we include that // in our critical-path bundle, but then we lazily load modules for /a and /b so // they don't load until the user navigates to those routes let routes = createRoutesFromElements( <Route path="/" element={<Layout />}> <Route index element={<Home />} /> <Route path="a" lazy={() => import("./a")} /> <Route path="b" lazy={() => import("./b")} /> </Route> );
Then in your lazy route modules, export the properties you want defined for the route:
export async function loader({ request }) { let data = await fetchData(request); return json(data); } // Export a `Component` directly instead of needing to create a React Element from it export function Component() { let data = useLoaderData(); return ( <> <h1>You made it!</h1> <p>{data}</p> </> ); } // Export an `ErrorBoundary` directly instead of needing to create a React Element from it export function ErrorBoundary() { let error = useRouteError(); return isRouteErrorResponse(error) ? ( <h1> {error.status} {error.statusText} </h1> ) : ( <h1>{error.message || error}</h1> ); }
An example of this in action can be found in the
examples/lazy-loading-router-provider
directory of the repository.🙌 Huge thanks to @rossipedia for the Initial Proposal and POC Implementation.
Patch Changes
- Fix
generatePath
incorrectly applying parameters in some cases (#10078)
1.3.3
Patch Changes
- Correctly perform a hard redirect for same-origin absolute URLs outside of the router
basename
(#10076) - Ensure status code and headers are maintained for
defer
loader responses increateStaticHandler
'squery()
method (#10077) - Change
invariant
to anUNSAFE_invariant
export since it's only intended for internal use (#10066) - Add internal API for custom HMR implementations (#9996)
1.3.2
Patch Changes
- Remove inaccurate console warning for POP navigations and update active blocker logic (#10030)
- Only check for differing origin on absolute URL redirects (#10033)
1.3.1
Patch Changes
- Fixes 2 separate issues for revalidating fetcher
shouldRevalidate
calls (#9948)- The
shouldRevalidate
function was only being called for explicit revalidation scenarios (after a mutation, manualuseRevalidator
call, or anX-Remix-Revalidate
header used for cookie setting in Remix). It was not properly being called on implicit revalidation scenarios that also apply to navigationloader
revalidation, such as a change in search params or clicking a link for the page we're already on. It's now correctly called in those additional scenarios. - The parameters being passed were incorrect and inconsistent with one another since the
current*
/next*
parameters reflected the staticfetcher.load
URL (and thus were identical). Instead, they should have reflected the the navigation that triggered the revalidation (as theform*
parameters did). These parameters now correctly reflect the triggering navigation.
- The
- Respect
preventScrollReset
on<fetcher.Form>
(#9963) - Do not short circuit on hash change only mutation submissions (#9944)
- Remove
instanceof
check fromisRouteErrorResponse
to avoid bundling issues on the server (#9930) - Fix navigation for hash routers on manual URL changes (#9980)
- Detect when a
defer
call only contains critical data and remove theAbortController
(#9965) - Send the name as the value when url-encoding
File
FormData
entries (#9867)
1.3.0
Minor Changes
- Added support for navigation blocking APIs (#9709)
- Expose deferred information from
createStaticHandler
(#9760)
Patch Changes
- Improved absolute redirect url detection in actions/loaders (#9829)
- Fix URL creation with memory histories (#9814)
- Fix
generatePath
when optional params are present (#9764) - Fix scroll reset if a submission redirects (#9886)
- Fix 404 bug with same-origin absolute redirects (#9913)
- Support
OPTIONS
requests instaticHandler.queryRoute
(#9914)
1.2.1
Patch Changes
- Include submission info in
shouldRevalidate
on action redirects (#9777, #9782) - Reset
actionData
on action redirect to current location (#9772)
1.2.0
Minor Changes
- Remove
unstable_
prefix fromcreateStaticHandler
/createStaticRouter
/StaticRouterProvider
(#9738)
Patch Changes
- Fix explicit
replace
on submissions andPUSH
on submission to new paths (#9734) - Fix a few bugs where loader/action data wasn't properly cleared on errors (#9735)
- Prevent
useLoaderData
usage inerrorElement
(#9735) - Skip initial scroll restoration for SSR apps with
hydrationData
(#9664)
1.1.0
This release introduces support for Optional Route Segments. Now, adding a ?
to the end of any path segment will make that entire segment optional. This works for both static segments and dynamic parameters.
Optional Params Examples
- Path
lang?/about
will match:/:lang/about
/about
- Path
/multistep/:widget1?/widget2?/widget3?
will match:/multistep
/multistep/:widget1
/multistep/:widget1/:widget2
/multistep/:widget1/:widget2/:widget3
Optional Static Segment Example
- Path
/home?
will match:/
/home
- Path
/fr?/about
will match:/about
/fr/about
Minor Changes
- Allows optional routes and optional static segments (#9650)
Patch Changes
- Stop incorrectly matching on partial named parameters, i.e.
<Route path="prefix-:param">
, to align with how splat parameters work. If you were previously relying on this behavior then it's recommended to extract the static portion of the path at theuseParams
call site: (#9506)
// Old behavior at URL /prefix-123
<Route path="prefix-:id" element={<Comp /> }>
function Comp() {
let params = useParams(); // { id: '123' }
let id = params.id; // "123"
...
}
// New behavior at URL /prefix-123
<Route path=":id" element={<Comp /> }>
function Comp() {
let params = useParams(); // { id: 'prefix-123' }
let id = params.id.replace(/^prefix-/, ''); // "123"
...
}
- Persist
headers
onloader
request
's after SSR documentaction
request (#9721) - Fix requests sent to revalidating loaders so they reflect a GET request (#9660)
- Fix issue with deeply nested optional segments (#9727)
- GET forms now expose a submission on the loading navigation (#9695)
- Fix error boundary tracking for multiple errors bubbling to the same boundary (#9702)
1.0.5
Patch Changes
- Fix requests sent to revalidating loaders so they reflect a
GET
request (#9680) - Remove
instanceof Response
checks in favor ofisResponse
(#9690) - Fix
URL
creation in Cloudflare Pages or other non-browser-environments (#9682, #9689) - Add
requestContext
support to static handlerquery
/queryRoute
(#9696)- Note that the unstable API of
queryRoute(path, routeId)
has been changed toqueryRoute(path, { routeId, requestContext })
- Note that the unstable API of
1.0.4
Patch Changes
- Throw an error if an
action
/loader
function returnsundefined
as revalidations need to know whether the loader has previously been executed.undefined
also causes issues during SSR stringification for hydration. You should always ensure youloader
/action
returns a value, and you may returnnull
if you don't wish to return anything. (#9511) - Properly handle redirects to external domains (#9590, #9654)
- Preserve the HTTP method on 307/308 redirects (#9597)
- Support
basename
in static data routers (#9591) - Enhanced
ErrorResponse
bodies to contain more descriptive text in internal 403/404/405 scenarios
1.0.3
Patch Changes
- Fix hrefs generated when using
createHashRouter
(#9409) - fix encoding/matching issues with special chars (#9477, #9496)
- Support
basename
and relative routing inloader
/action
redirects (#9447) - Ignore pathless layout routes when looking for proper submission
action
function (#9455) - properly support
index
routes with apath
inuseResolvedPath
(#9486) - Add UMD build for
@remix-run/router
(#9446) - fix
createURL
in local file execution in Firefox (#9464) - Updates to
unstable_createStaticHandler
for incorporating into Remix (#9482, #9465)
1.0.2
Patch Changes
- Reset
actionData
after a successful action redirect (#9334) - Update
matchPath
to avoid false positives on dash-separated segments (#9300) - If an index route has children, it will result in a runtime error. We have strengthened our
RouteObject
/RouteProps
types to surface the error in TypeScript. (#9366)
1.0.1
Patch Changes
- Preserve state from
initialEntries
(#9288) - Preserve
?index
for fetcher get submissions to index routes (#9312)
1.0.0
This is the first stable release of @remix-run/router
, which provides all the underlying routing and data loading/mutation logic for react-router
. You should not be using this package directly unless you are authoring a routing library similar to react-router
.
For an overview of the features provided by react-router
, we recommend you go check out the docs, especially the feature overview and the tutorial.
For an overview of the features provided by @remix-run/router
, please check out the README
.