GOVERNANCE.md
React Router has been around since 2014 largely under the development and oversight of Michael Jackson and Ryan Florence. After the launch of Remix in 2021, the subsequent creation of the Remix team, and the merging of Remix v2 into React Router v712, the project shifted from a Founder-Leader model to a "Steering Committee" (SC) model that operates on a Request for Comments (RFC) process.
This document will outline the process in which React Router will continue to evolve and how new features will make their way into the codebase. This is an evergreen document and will be updated as needed to reflect future changes in the process.
The following design goals should be considered when considering RFCs for acceptance:
declarative -> data -> framework) and then leveraged by the higher-level modes. This ensures that the largest number of React Router applications can leverage them.The Steering Committee will be in charge of accepting RFC's for consideration, approving PRs to land features in an "unstable" state, and approving stabilization PRs to land PRs that stabilize features into React Router.
The SC will initially consist of the Remix team developers:
@brophdawg11)@pcattori)@markdalgleish)@jacob-ebey)@brookslybrand)@sergiodxa)@rossipedia)In the future, we may add a limited number of heavily involved community members to the SC as well.
To reduce friction, the SC will primarily operate asynchronously via GitHub, but private and/or public meetings may be scheduled as needed.
Due to the large number of React Router applications out there, we have to be a bit strict on the process for filing issues to avoid an overload in GitHub.
bug-report-test.tsnpx create-react-router app is acceptableAccepting PRs labelThe process for new features being added to React Router will follow a series of stages loosely based on the TC39 Process. It is important to note that entrance into any given stage does not imply that an RFC will proceed any further. The stages will act as a funnel with fewer RFCs making it into later stages such that only the strongest RFCs make it into a React Router release in a stable fashion.
[!NOTE] Most new community-driven features for React Router will go through all stages. Some features, if trivial or obvious enough, may skip stages and be implemented directly as a stable feature.
This table gives a high-level overview of the stages, but please see the individual stage sections below for more detailed information on the stages and the process for moving an FC through them. Once a feature reaches Stage 2, it will be added to the Roadmap where it can be tracked as it moves through the stages.
| Stage | Name | Entrance Criteria | Purpose |
|---|---|---|---|
| 0 | Proposal | Proposal discussion opened on GitHub | We start with a GitHub Proposal to provide the lowest barrier to RFC submission. Anyone can submit an RFC and community members can review, comment, up-vote without any initial involvement of the SC. |
| 1 | Consideration | Proposal acceptance from 2 SC members | The consideration phase is the first "funnel" for incoming RFCs where the SC can officially express interest in the more popular RFCs. We only require 2 SC members to express interest to move an RFC into the Consideration phase to allow for low-friction experimentation of features in the Alpha stage. |
| 2 | Alpha | Pull request (PR) opened to implement the feature in an "unstable" state | The Alpha stage is the next funnel for RFCs. Once interest has been expressed by the SC in the Consideration phase we open the RFC up for a sample PR implementation and a mechanism for community members to alpha test the feature without requiring that anything be shipped in a React Router SemVer release. This stage allows evaluation of the RFC in running applications and consideration of what a practical implementation of the RFC looks like in the code. |
| 3 | Beta | PR approval from 2 SC members indicating their acceptance of the PR for an unstable API | A RFC enters the Beta stage once enough members of the SC feel comfortable not only with the code for the beta feature, but have also seen positive feedback from alpha testers that the feature is working as expected. Once an Alpha stage PR has enough SC approvals, it will be merged and be included in the next React Router release. |
| 4 | Stabilization | At least 1 month in the Beta stage and PR opened to stabilize the APIs. This PR should also include documentation for the new feature. | The Stabilization phase exists to ensure that unstable features are available for enough time for applications to update their React Router version and opt-into beta testing. We don't want to rush features through beta testing so that we have maximal feedback prior to stabilizing a feature. |
| 5 | Stable | PR approval from at least 50% of the SC members indicating their acceptance of the PR for a stable API | A RFC is completed and enters the Stable stage once enough members of the SC feel comfortable not only with the code for the stable feature, but have also seen positive feedback from beta testers that the feature is working as expected. Once an Beta stage PR has enough SC approvals and has spent the required amount of time in the Beta stage, it can be merged and included in the next React Router release. |
accepting-prs label to the RFC if we are open to community PRsunstable_ prefix on the future flag or API)unstable_ stateaccepting-prs label and add the 🗺️ Roadmap label to indicate that this RFc is officially on the roadmapalpha-release label, which will kick off an experimental release and comment it back on the PRdev but not yet released in a stable version, it may not be ideal for testing in all cases.patch file in a comment that folks can use via patch-package or pnpm patchdev
unstable_ PR counts as an implicit approval, so in those cases explicit approval is required from 1 additional SC membernightly releases and the next normal SemVer release for broader beta testing under the unstable_ flagunstable_ prefixes and stabilize the featuredev
nightly releases and the next normal SemVer releaseThis section captures the notes from the React Router Steering Committee meetings:
<!-- TEMPLATE <details> <summary>YYYY-MM-DD Meeting Notes</summary> ... </details> --> <details> <summary>2025-09-23 Meeting Notes</summary>Summary
Brooks announced the planned release of unstable framework RSC support in 7.9.2 and the fetcher.unstable_reset() API. Matt and Pedro discussed splitting Ryan's proposal for useRouteLoaderData type-safety to separate "router data" from "route data." Bryan and Matt reviewed the proposal for new instrumentation APIs. Matt and Jacob decided to close several issues related to ESLint configuration, OpenTelemetry, and module federation.
Details
fetcher.unstable_reset() APIinstrumentRouter/instrumentRoutes APIs should be sufficient for various implementations of logging/tracing layered on topperformance.mark/measure), but will lean on the community to provide packages for specific observability approachesSummary
Matt, Bryan, Mark, and Pedro discussed the progress of various features, including middleware, context, the onError feature, and RSC framework mode, with most nearing completion or already released. Matt and Bryan also explored the integration of observability and OpenTelemetry with Sentry and React Router, considering OpenTelemetry as a potential standard for JavaScript monitoring. The team decided to focus on current in-progress items instead of reviewing and accepting additional proposals because there are already 10+ proposals in-progress.
Details
onError feature, released in 7.8.2, is working as expected and providing anticipated datauseRouterState hook, noting that Ryan's attention is elsewhere, but they are interested in revisiting it for type safety and potentially replacing the useRouteLoaderData hookuseMatches API is problematic, especially concerning type safety, and suggested finding a solution that does not rely on ituseRouterState().matches to be less type-safe than Route.ComponentProps["matches"]Action Items
fetcher.reset() work after 7.9.0 is released ()<Link onPrefetch> task soonThe SC reviewed items on the open Proposal for React Router v8
require(esm) feature is not behind an experimental flaguseRouterState into v8 as the other half of the unstable_useRoute coinunstable_optimizedDeps feature, confirming it will remain unstable in V8 and then be pseudo-deprecated in favor of RollDown
Vite environment API and split route modules are nearing stabilizationMatt mentioned opening Pull Requests (PRs) to stabilize fetcher.reset and onError for client-side use
Stabilizing Fetcher Reset and Client-Side Error Handling
Matt mentioned opening Pull Requests (PRs) to stabilize fetcher.reset and onError for client-side use. For client-side onError, Matt made a change to align it with handleError on the server by passing the location and params to the error handler, with the goal of not holding off on stabilizing the API . Bryan suggested considering adding the pattern to the error information, which Matt agreed would be useful for filtering errors in Sentry.
Stabilizing Other Router APIs
Matt confirmed with Mark that both split route modules and the environment API are ready to be stabilized. The intent is to batch these stabilizations into a minor release. Other features like observability and a transition flag are still too new for stabilization
Opt-Out Flag for Custom Navigations
Matt discussed fast-tracking a flag to allow users to opt out of wrapping their own navigations in startTransition. This is needed because the current implementation has bugs related to navigation state not surfacing when custom navigations are wrapped in startTransition, particularly affecting useSyncExternalStore and causing minor tearing issues with search params. Matt mentioned we could fork off and ship the false (opt-out) version of this unstable flag quickly if needed.
Type-Safe Fetchers Discussion
The discussion moved to the highly-anticipated type-safe fetchers feature. Bryan suggested that the definition of a fetcher should be tied to a route at creation time, as fetchers resolve to a single handler (action or loader), where all the type signature glue happens . A challenge is resolving ambiguity when a path matches multiple loaders, such as a layout route and an index route.
Route ID vs. Pattern for Type-Safe Fetchers
The group debated using route ID versus the path pattern for identifying the route. Bryan and Jacob agreed that parameters should be accepted at the invocation site of the fetcher. Mark raised concerns that using route ID might require querying the full manifest, which could be problematic with "fog of war" architectures where only a small number of routes are known at runtime. They agreed to use the pattern instead, which doubles as the route ID in a sense and does not require querying the manifest for URL construction.
Proposed New Hooks for Type-Safe Fetchers
Bryan proposed splitting useFetcher into two separate hooks: useRouteLoader and useRouteAction, bound explicitly to a loader and an action, respectively. This separation is beneficial because a loader is primarily concerned with the GET method, while an action can handle multiple methods (POST, PUT, etc.). The new hooks would return an array with the state/data object and the imperative method (like submit or load), a pattern which Matt and Mark liked.
Streamlining State Tracking with React APIs
The conversation shifted towards aligning the new hooks with modern React APIs, especially those from React 19. Bryan suggested that the router could offload state tracking to userland by using React's useTransition and useOptimistic hooks, leading to a much slimmer abstraction. This would replace the existing fetcher.state (idle, loading, submitting) and fetcher.form.
Leveraging useActionState and Form Actions
Jacob noted that using an asynchronous function for a form's action would also allow for use of the useActionState hook from React, which can track the pending status of the form, further streamlining the API. This design would also enable the deprecation of fetcher.form in favor of a standard React form. However, the group noted that this approach would be for JS-supported forms and not progressively enhanced forms in an RSC world without JavaScript.
Call Site Revalidation Opt-Out
The group discussed the community PR for a call site revalidation opt-out, specifically the open question of whether a call site option like shouldRevalidate: false should override a route's existing shouldRevalidate function. Jacob and Bryan agreed with Sergio's recommendation that the call site option should set the new default value passed to the routes, essentially bubbling up, to avoid potential support headaches and data integrity issues that would arise from overriding all revalidation.
Naming Convention for Revalidation Opt-Out
Bryan suggested a minor design point to name the option passed to submit as defaultShouldRevalidate for consistency. Matt agreed with this suggestion.
Default Route Revalidation Behavior Bryan and Matt discussed the implementation of a default setting for route revalidation, with Bryan expressing concern that people might misuse a hard bypass but agreeing that passing in a default allows the route logic to still determine the revalidation. Matt highlighted that this default would be an easy win for applications with many routes that need a common revalidation behavior, preventing the need to change fifty routes, while still allowing specific routes with their own logic to override the default. They agreed that the route should always be the final authority on revalidation.
Scope and Granularity of Default Revalidation
Jacob proposed setting a specific route default, perhaps for parent routes but not a view, for scenarios involving submissions, but Matt dismissed this, noting that routes already have a specific place for their logic in the route function. Bryan also suggested that more granular control would lead to excessive complexity. The team concluded that the default should not allow a function, as Jacob argued it should be derived from local state.
Implementation of 'default revalidate'
Matt confirmed liking the name "default revalidate" and determined that it should apply to all imperative hooks, including navigates and submits. Bryan and Matt agreed that having the routes maintain final control of revalidation makes sense on navigates. Matt mentioned that there is an existing Pull Request for this feature, which they plan to update.
</details> <details> <summary>2025-12-02 Meeting Notes</summary>Meeting Status and Stabilizations
Matt shared that three stabilizations were pushed out: environment API future flag, split route modules future flag, and fetch error reset. Matt noted that onError would be in the next release after one last internal refactor to address potential double reporting issues in strict mode.
useRoute and Type-Safe Fetchers
Matt discussed the plan to complete the other half of useRoute, the useRouterState functionality, and treating them as a package deal for stabilization. Pedro agreed, emphasizing the need for the type-safe fetcher approach to be cohesive with useRoute before stabilization, to avoid having multiple ways of doing things if the ID-based approach is changed later.
Babel to SWC/Oxide Migration for Performance
Mark raised the proposal to switch from Babel to SWC/Oxide for speed improvements, noting that the stabilization of split route modules increases the amount of transformations happening in Babel. Pedro expressed support for making things faster but questioned the priority, as they have yet to see a real use case where Babel is the bottleneck, suggesting current HMR times are sub-40 milliseconds.
Performance Bottlenecks and Rollup Integration
Pedro explained that a larger architectural problem, the necessity of a full pass over all routes for manifest creation, contributes to performance issues, making the dev server launch time proportional to the number of routes. Mark clarified that Rollup speeds up the build, not dev, and Pedro suggested profiling to determine if Babel is truly the bottleneck out of the 50 milliseconds of overhead. Matt suggested involving the community for hard numbers and potentially waiting to see if Rollup with an AST pipeline API would alleviate the issue, which might necessitate rewriting transforms anyway. Matt asked Mark and Pedro to comment on the proposal, indicating interest but needing more evidence of the bottleneck.
Fetcher Error Handling and Imperative Usage
Discussion returned to an existing, highly voted proposal concerning fetcher error handling and completion. Matt noted that returning promises from fetcher.load and fetcher.submit partially solved the completion concern, but returning the data is still missing. The other main request is to prevent fetcher errors from triggering the route-level error boundary, for which Matt suggested an opt-in mechanism like don't bubble errors or a handle error option. Bryan argued that fetchers, being out-of-band network requests, should not bubble up to the route error boundary naturally.
Inline Action Approach for Fetcher Error Handling
The discussion moved towards an inline action approach for error handling, aligning with client loader mechanisms, as suggested by Jacob. Matt and Bryan considered how an inline handler could allow users to catch network errors and decide whether to return errors as data or throw. Sergio Daniel Xalambrí questioned whether these changes should be applied to the new type-safe fetchers (e.g., useRoute action) instead of evolving useFetcher. Matt and Bryan agreed that implementing this work within the new type-safe fetcher APIs, where submit would return data and reject on errors, seems like the most appropriate approach.
Route Masking/Rewrites for Modals
Matt introduced a resurfaced, high-priority proposal for route masking/rewriting, similar to Next.js's parallel/intercepting routes or Tanstack's route masking. This feature, previously available in declarative mode, allows rendering a modal over a background URL while maintaining a different URL in the bar. Matt suggested an API where the user provides the URL to be displayed in the URL bar, and the router navigates internally to a different URL, likely driven by search parameters. Mark and Bryan agreed that this seems coupled to client-side navigation for UX cases like job details over search results.
Server Rewrites and Client Router Synchronization
Sergio Daniel Xalambrí noted that people often ask for server rewrites, which in Next.js terms often means URL aliases where a path renders the content of another route, potentially with param rewriting. Matt concluded that true server rewrites are a separate feature but noted that implementing the client-side masking feature would close the gap on what is needed to synchronize rewrite logic with the client router, potentially unlocking future server-side rewrite capabilities. Matt intends to update the proposal and move it to the "accepting PRs" stage, noting that the implementation could draw on internals from V6.
Element Scroll Restoration
The last topic discussed was a high-voted proposal for scroll restoration on elements other than the window. Matt explained that a full userland implementation is not reliably possible because the router is the only one that truly knows the moment right before a view changes to reliably capture the scroll position. Matt plans to provide guidance and feedback based on previous PR discussions, hoping the community can finalize the implementation.
</details> <details> <summary>2025-12-16 Meeting Notes</summary>Trailing Slash Consistency Bug Fix
Matt outlined a bug fix related to inconsistent request path names provided to the loader/action on data requests when a URL has a trailing slash. Matt explained that the solution, developed with Jacob, involves changing the format of the data request URL for trailing slash scenarios to resemble the _root.data format, which is being put behind a future flag due to potential breaking changes and cache rule implications. The new format will use /a/b/c/_.data when the URL is /a/b/c/.
Matt also noted that the future flag provides an opportunity to collapse the _root.data format into the new trailing slash format, resulting in two standardized formats for data requests in the future. Bryan asked for clarification on various URL and query parameter configurations, and Matt explained the distinction between the new trailing slash format and the use of the index query parameter, confirming that the new format will be opt-in and mostly non-breaking for users upon adoption