Earlier this year we started an undertaking to Remix React Router with the aim of bringing all of the Remix Data API's (loaders
, actions
, fetchers
, etc.) over to React Router. With the recent release of React Router v6.4.0 we're proud to report that we've completed that effort...and we think we've made them even better π. Not only have we fixed a few edge case bugs, but we've stabilized some APIs and introduced some really great new ones. Here's a quick overview of the changes but we encourage you to check out the blog post for more information.
useRevalidator
defer
/Await
useRouteLoaderData
unstable_shouldReload
has stabilized as shouldRevalidate
<ScrollRestoration getKey>
method allows finer-grained control over scroll restorationerrorElement
fetcher.load
calls now participate in revalidation - as they should have all along!Now we get to flip the script and start React Router-ing Remix so we can bring these changes back over to Remix users (and delete a bunch of Remix code in the process). The good news for all you folks is that we plan to do it iteratively and without a major release π€―. We thought the approach we're using was pretty cool so we wanted to share it with you all.
You can think of Remix's architecture as having 4 primary aspects:
These 4 sections also happen to be nicely decoupled, so they provide us clean boundaries to approach this in an iterative fashion to avoid a single big-bang release. This should mean a much smoother integration path for Remix users!
We'll first update Remix's server runtime to use React Router's new unstable_createStaticHandler
to do server-side data-fetching, and once we're comfortable we can release that without touching steps 2 through 4. Even better - we can break this down into individual efforts for resource route requests, client-side navigation data fetch requests, and document requests.
Note: createStaticHandler
is published as unstable in 6.4.0
in case we come across required changes during this process. We'll stabilize it once we've completed the Remix integration.
We're planning to do these one by one using Martin Fowler's Strangler Fig pattern so we can gain the highest confidence that we haven't introduced any regressions (shout out to @DavidKPiano
for re-surfacing this pattern in my brain a few months ago!). If you're unfamiliar with this pattern, the general gist of it is that you write the new code alongside the old code and slowly switch portions over. We can take this even further with a feature-flagged approach that keeps both paths active and allows for validations during tests and at runtime.
Here's a simplified example of what this might look like for a resource route request in Remix
function handleResourceRouteRequest({ request }) {
// If the flag is enabled, clone the request so we can use it twice
let response = processResourceRouteRequest(
ENABLE_NEW_STUFF ? request.clone() : request,
);
// When our flag is enabled, send this request through the new
// code path, while also asserting that we get back an identical
// response
if (ENABLE_NEW_STUFF) {
let newResponse = processResourceRouteRequestNew(request);
assertResponses(response, newResponse);
return newResponse;
}
return response;
}
This approach gives us a number of benefits:
dev
branch instead of maintaining a long-lived feature branchOnce we're done wth server-side data fetching, we can move onto server-side HTML rendering (using React Router's unstable_StaticRouterProvider
). This is another aspect we can (sort of) do independently. We can render HTML on the server using the new APIs, but we won't be able to hydrate that on the client (using the old APIs) without some nasty code forks to determine whether to read from the old or new contexts. Thankfully, idiomatic Remix apps work without JavaScript, so we can validate our tests and apps without JS during this step. We obviously won't ship after Step 2, but we'll be able to gain a pretty high level of confidence in our SSR before moving onto step 3. Again we'll use a flag to enable both paths and perform some level of HTML assertions between the old and new.
The fun part here is that this step is where we start to fully realize Michael's vision of Remix being a "compiler for React Router". Now that react-router knows how to do all the cool data fetching stuff, Remix just compiles a set of conventional route files on disk into the appropriate routes expected by React Router. Once those routes are created, it just hands them off to React Router for the heavy lifting πͺ!
On to client-side hydration! This is where we'll remove the vast majority of the Remix code (bye-bye Transition Manager - we'll always love you π). In the same vein as above, Remix simply needs to leverage the route manifest provided by the server to generate a route tree to hand off to createBrowserRouter
and then RouterProvider
does the rest. The super cool part is that Remix gets to use the exact same loader and action for all of it's routes, since all they do is make a fetch
to the Remix server with a _data
param! This probably won't go through the feature flag since we can't exactly hydrate the document twice π€·ββοΈ.
This might be the most incorrectly asserted phrase in software development, but we're going to be stubborn here and use it again -- once we finish step 3, client side routing and data-fetches should Just Workβ’οΈ since that's entirely handled by React Router 6.4 now!
We should note again that we're planning to do this all as minor Remix 1.x releases (likely one release for step 1, and another for steps 2-4). To maintain backwards compatibility, there will be some work to do in steps 2 and 3. Here's a few examples:
useTransition
has been renamed to useNavigation
in React Router 6.4, so we'll mark useTransition
as deprecated but keep it aroundsubmission
field and removed the type
field from navigations (formerly transitions) and fetchers, so we'll use wrapper hooks to add those back to useTransition
and useFetcher
Where possible (and we hope this is all cases), we will add feature flags to remix.config.js
allowing you to opt-into the new Remix v2 behavior at your convenience, while providing backwards compatible-behavior if you do not opt-in.
We're really excited for this next step, and even more excited about some of the possibilities it opens up for the future of both React Router and Remix (did somebody say Preact?). Please keep an eye on the repos for updates and as always, hit us up on Discord or Twitter if you have any questions or excitement about any or all of this :)