Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5029648
Merge branch 'release' into dev
brophdawg11 May 5, 2026
6b01f85
chore: format
remix-run-bot May 5, 2026
522bc1b
Add unstable_useRouterState hook (#15017)
brophdawg11 May 7, 2026
9f01e02
chore: format
remix-run-bot May 7, 2026
5700613
chore: generate markdown docs from jsdocs
remix-run-bot May 7, 2026
a907779
fix: fire onError for errors already present in initial router state …
karthik-idikuda May 7, 2026
041cd32
fix(react-router): Internal preloads refactor to preserve types (#14860)
grzdev May 7, 2026
954a4a6
Fix stale SSR data when hydration is aborted by a same-route navigati…
brophdawg11 May 7, 2026
aabd30c
Use shared isMutationMethod check (#15033)
brophdawg11 May 11, 2026
7e6725a
Cleanup lint issues (#15030)
brophdawg11 May 11, 2026
a90d184
fix: resolve basename/base conflict when basename matches app directo…
brophdawg11 May 11, 2026
90040d9
chore: format
remix-run-bot May 11, 2026
9fd7b7c
Update release comments workflow
brophdawg11 May 11, 2026
0041c69
Disable caching on publish workflow
brophdawg11 May 12, 2026
44c3478
fix: prevent fetcher formData flicker and eliminate state.fetchers mu…
brophdawg11 May 13, 2026
6bf91ce
chore: format
remix-run-bot May 13, 2026
fadd6c4
Merge branch 'main' into release
brophdawg11 May 13, 2026
4322e58
Update docs for useRouterState
brophdawg11 May 13, 2026
b887b3b
Remove pnpm cachig from release workflow
brophdawg11 May 13, 2026
e62c3f1
Fix changelog PR number detection logic
brophdawg11 May 13, 2026
89996bd
Fire onError for initial-load errors when RouterProvider mounts late …
brophdawg11 May 13, 2026
587d08f
Release v7.15.1 (#15038)
ryanflorence May 14, 2026
a6ab746
Merge branch 'release'
brophdawg11 May 14, 2026
2df87ca
chore: format
remix-run-bot May 14, 2026
427409f
Update release notes
brophdawg11 May 14, 2026
caaae65
Fix roadmap label in release-comments
brophdawg11 May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions .agents/skills/implement-rfc/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
---
name: implement-rfc
description: Implement a React Router RFC from a GitHub discussion URL. Fetches the proposal, evaluates community feedback, resolves outstanding questions interactively, then implements the feature with tests, future flags (if breaking), and a changeset.
disable-model-invocation: true
---

# Implement React Router RFC

Implement the RFC from the following GitHub discussion: $ARGUMENTS

## Branching

RFC implementations should start from a clean working tree. If there are uncommitted changes, stop and ask me to resolve them before continuing.

- If you are already on a named branch that is at the same HEAD as `dev`, use that branch.
- Otherwise, create a branch from `dev` using the format `{author}/rfc-{semantic-name}`:
```sh
git branch {author}/rfc-{semantic-name} dev
git checkout {author}/rfc-{semantic-name}
```

## Workflow

### 1. Fetch and Understand the RFC

Use `WebFetch` to read the discussion URL. If a GitHub discussion number is given instead of a URL, construct:
`https://github.com/remix-run/react-router/discussions/<number>`

Extract:

- **Problem being solved**: what pain point does this RFC address?
- **Proposed API**: exact function signatures, hook names, types, option shapes
- **Affected modes**: Declarative / Data / Framework / RSC Data / RSC Framework
- **Breaking changes**: does this change or remove existing public API?
- **Open questions**: anything explicitly marked as unresolved, "TBD", or asked as a question in the proposal
- **Status**: are there linked tracking issues? Look for links to other github issues and read them to see if there is additional context.

```sh
gh issue view <number> --repo remix-run/react-router
```

### 2. Evaluate Community Feedback

Fetch all comments from the discussion:

Use `WebFetch` on `https://github.com/remix-run/react-router/discussions/<number>` and scroll through the full thread. Look for:

- **Concerns or objections** raised by community members or maintainers
- **Alternative proposals** or API shape suggestions
- **Edge cases** raised that the proposal does not address
- **Positive signals** — repeated praise for a specific approach signals it's the right direction
- **Maintainer responses** — Ryan Florence, Michael Jackson, or other core team members clarifying intent

Summarize the community sentiment into:

- Points of consensus (safe to proceed)
- Points of contention (need resolution before implementing)
- Unanswered questions from the original proposal

### 3. Resolve Outstanding Questions

Before writing any code, present me with a numbered list of every unresolved question — from both the RFC itself and from community feedback. For each question:

- State the question clearly
- Summarize relevant community input
- Offer a recommended answer with reasoning

Ask me to confirm, override, or skip each question. Do not proceed to implementation until all questions are either answered or explicitly deferred.

Example format:

```
## Unresolved Questions

1. **Should `useRouterState()` accept a path argument for scoped matching?**
Community feedback: 3 comments in favor, 1 against (concerns about complexity).
Recommendation: Yes — scoped matching improves type safety for nested routes.
→ Your decision: [confirm / override / defer]

2. **What should happen when the path doesn't match the current location?**
Recommendation: Return `null` for active state (consistent with `useMatch()`).
→ Your decision: [confirm / override / defer]
```

Save the resolved decisions to a scratch file at `tasks/rfc-decisions.md` for reference during implementation.

### 4. Plan the Implementation

Before writing code, produce a concise implementation plan covering:

- New types/interfaces to add
- New functions/hooks to implement and their file locations
- Existing APIs to deprecate (mark with `@deprecated` JSDoc + console warning in dev)
- Whether a future flag is needed (see §5 below)
- Test files to create or extend (unit and/or integration)
- Changeset bump level (`minor` for new features, `major` for breaking changes behind a future flag that is now defaulted on)

Present the plan to me and wait for approval before implementing.

### 5. Future Flags for Breaking Changes

If the RFC changes or removes existing public API behavior, it **must** ship behind a future flag which will start with an `unstable_` prefix.

**Future flag pattern:**

1. Add the flag to `FutureConfig` in `packages/react-router/lib/router/utils.ts`:

```ts
export interface FutureConfig {
// existing flags...
unstable_myNewBehavior: boolean;
}
```

2. Gate the new behavior on the flag:

```ts
if (router.future.unstable_myNewBehavior) {
// new behavior
} else {
// legacy behavior
}
```

3. Document the flag in `docs/upgrading/future-flags.md` if it exists.

New additive APIs (no behavior change to existing code) do **not** need a future flag.

### 6. Key File Locations

| Area | Files |
| --------------------- | -------------------------------------------- |
| Core router logic | `packages/react-router/lib/router/router.ts` |
| Router types/utils | `packages/react-router/lib/router/utils.ts` |
| React components | `packages/react-router/lib/components.tsx` |
| React hooks | `packages/react-router/lib/hooks.tsx` |
| Public exports | `packages/react-router/index.ts` |
| DOM utilities | `packages/react-router/lib/dom/` |
| Framework/Vite plugin | `packages/react-router-dev/vite/plugin.ts` |
| RSC runtime | `packages/react-router/lib/rsc/` |
| Unit tests | `packages/react-router/__tests__/` |
| Integration tests | `integration/` |
| Future flags doc | `docs/upgrading/future-flags.md` |

Confirm existing patterns before writing new code - prefer using the LSP but `Grep`/`Glob` also work. Match naming conventions and code style exactly.

### 7. Implement the Feature

Follow the approved plan. For each logical unit of work:

1. Write the implementation
2. Export from the appropriate public entry point (`packages/react-router/index.ts`)
3. Add `@deprecated` JSDoc to any APIs being superseded
4. Run typecheck to catch type errors early:
```sh
pnpm typecheck
```

Keep changes minimal and focused. Do not refactor unrelated code. Commit as often as needed.

### 8. Write Tests

**Unit tests** (for hooks, pure router logic, component behavior — no build):

- Location: `packages/react-router/__tests__/`
- Runner: Jest → `pnpm test packages/react-router/__tests__/<file>`
- Cover: happy path, edge cases identified in RFC/community feedback, future flag gating (if applicable), deprecation warnings

**Integration tests** (for Vite/Framework Mode, SSR, hydration):

- Location: `integration/`
- Runner: Playwright → `pnpm test:integration:run --project chromium integration/<file>`
- Required if the RFC touches Framework Mode, file-system routing, or SSR behavior

Run all tests and confirm they pass:

```sh
pnpm test packages/react-router/
pnpm test:integration:run --project chromium # only if integration tests were added/changed
```

### 9. Lint and Typecheck

```sh
pnpm lint
pnpm typecheck
```

Fix all errors before proceeding.

### 10. Create a Change File

Create `packages/<package>/.changes/<bump-level>.<descriptive-name>.md`. Use the RFC title or tracking issue as the description:

```markdown
feat: <brief description matching the RFC title>

Implements the `useRouterState()` RFC (#12358). Deprecates `useLocation`,
`useParams`, `useSearchParams`, `useNavigation`, `useMatches`, `useMatch`,
`useNavigationType`, and `useViewTransitionState` in favor of a unified API.

Enable the `unstable_consolidatedRouterState` future flag to opt in.
```

Bump levels:

- `patch` — bug-adjacent fix only
- `minor` — new additive API (no breaking changes)
- `major` — breaking change (should be rare; most breaking changes go behind a future flag as `minor` first)
- `unstable` — new API that is not yet stable (e.g. added in a future flag, or an experimental API that may be removed without a major bump)

### 11. Report and Review

Summarize:

- What RFC was implemented and which decisions were made
- New public APIs added (with brief usage example)
- APIs deprecated and the migration path
- Future flag name (if applicable) and how to opt in
- Test coverage added
- Anything deferred or explicitly out of scope

Ask me to review and iterate before opening a PR.

### 12. Commit

Once I approve, commit and open a PR to `dev`:

```sh
gh pr create --base dev --title "feat: <RFC title>" --body "..."
```

PR body should include:

- Link to the RFC discussion (Closes or Implements #NNNN)
- Summary of what was implemented
- Future flag instructions if applicable
- Testing notes
- Any decisions that deviated from the original proposal and why
3 changes: 1 addition & 2 deletions .github/workflows/release-comments.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: 💬 Release Comments
name: 💬 Release Comments (manual)

on:
workflow_call:
workflow_dispatch:

concurrency:
Expand Down
27 changes: 26 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,32 @@ jobs:
comment:
name: 📝 Comment on released issues/pull requests
needs: publish
uses: ./.github/workflows/release-comments.yml
runs-on: ubuntu-latest
permissions:
issues: write # enable commenting on released issues
pull-requests: write # enable commenting on released pull requests
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: 📦 Setup pnpm
uses: pnpm/action-setup@v6

- name: ⎔ Setup node
uses: actions/setup-node@v6
with:
node-version: 24 # Needed for node TS support
package-manager-cache: false

- name: 📥 Install deps
run: pnpm install --frozen-lockfile

- name: 📝 Comment on released issues and pull requests
env:
GH_TOKEN: ${{ github.token }}
run: pnpm run release-comments

experimental-release:
name: 🧪 Experimental Release
Expand Down
64 changes: 62 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ We manage release notes in this file instead of the paginated Github Releases Pa
<summary>Table of Contents</summary>

- [React Router Releases](#react-router-releases)
- [v7.15.1](#v7151)
- [v7.15.0](#v7150)
- [Stabilizations](#stabilizations)
- [Route matching optimizations](#route-matching-optimizations)
- [v7.14.2](#v7142)
- [v7.14.1](#v7141)
- [v7.14.0](#v7140)
Expand Down Expand Up @@ -170,6 +173,63 @@ We manage release notes in this file instead of the paginated Github Releases Pa

</details>

## v7.15.1

Date: 2026-05-14

### What's New

#### `useRouterState` (unstable)

Following our [Less is More](https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#design-goals) design goal, this release includes a new `unstable_useRouterState()` hook (Framework + Data Mode) that consolidates access to active and pending router states ([RFC](https://github.com/remix-run/react-router/discussions/12358), [Roadmap Issue](https://github.com/remix-run/react-router/issues/13073)).

This should allow you to consolidate usages of a bunch of different hooks which will likely be marked deprecated later on in v8 and potentially removed in an eventual v9:

```ts
let { active, pending } = unstable_useRouterState();

// Active is always populated with the current location
active.location; // replaces `useLocation()`
active.searchParams; // replaces `useSearchParams()[0]`
active.params; // replaces `useParams()`
active.matches; // replaces `useMatches()`
active.type; // replaces `useNavigationType()`

// Pending is only populated during a navigation
pending.location; // replaces `useNavigation().location`
pending.searchParams; // equivalent to `new URLSearchParams(useNavigation().search)`
pending.params; // Not directly accessible today
pending.matches; // Not directly accessible today
pending.type; // Not directly accessible today
pending.state; // replaces `useNavigation().state`
pending.formMethod; // replaces useNavigation().formMethod
pending.formAction; // replaces useNavigation().formAction
pending.formEncType; // replaces useNavigation().formEncType
pending.formData; // replaces useNavigation().formData
pending.json; // replaces useNavigation().json
pending.text; // replaces useNavigation().text
```

### Patch Changes

- `react-router` - Memoize `useFetchers` to return a stable identity and only change if fetchers changed ([#15028](https://github.com/remix-run/react-router/pull/15028))
- `react-router` - Update router to operate on fetcher Maps in an immutable manner to avoid delayed React renders from potentially reading an updated but not yet committed Map. This could result in brief flickers in some fetcher-driven optimistic UI scenarios ([#15028](https://github.com/remix-run/react-router/pull/15028))
- `react-router` - Fix `serverLoader()` returning stale SSR data when a client navigation aborts pending hydration before the hydration `clientLoader` resolves ([#15022](https://github.com/remix-run/react-router/pull/15022))
- `react-router` - Fix `RouterProvider` `onError` callback not being called for synchronous initial loader errors in SPA mode ([#15039](https://github.com/remix-run/react-router/pull/15039)) ([#14942](https://github.com/remix-run/react-router/pull/14942))
- `react-router` - Internal refactor to consolidate mutation request detection through shared utility ([#15033](https://github.com/remix-run/react-router/pull/15033))
- `@react-router/dev` - Fix `basename` conflicting with `app` directory name when Vite `base` is set ([#15027](https://github.com/remix-run/react-router/pull/15027))
- When the Vite `base` config and React Router `basename` both match the app directory name (e.g. `base: "/app/"`, `basename: "/app/"`), Vite would strip the base prefix from server-build virtual module import paths, causing "Failed to load url /root.tsx" errors
- The fix uses `/@fs/` absolute paths for those imports to bypass Vite's base-stripping logic

### Unstable Changes

⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_

- `react-router` - Add a new `unstable_useRouterState()` hook that consolidates access to active and pending router states (RFC: #12358) ([#15017](https://github.com/remix-run/react-router/pull/15017))
- Data/Framework/RSC only — throws when used without a data router

**Full Changelog**: [`v7.15.0...v7.15.1`](https://github.com/remix-run/react-router/compare/react-router@7.15.0...react-router@7.15.1)

## v7.15.0

Date: 2026-05-05
Expand Down Expand Up @@ -228,10 +288,10 @@ We've added a handful of route matching optimizations in this release for Framew
- `react-router` - Add `nonce` to `<Scripts>` `<link rel="modulepreload">` elements (if provided) ([af5d49b](https://github.com/remix-run/react-router/commit/af5d49b))
- `react-router` - Fix a bug with `unstable_defaultShouldRevalidate={false}` where parent routes that did not export a `shouldRevalidate` function could be incorrectly included in the single fetch call for new child route data ([#15012](https://github.com/remix-run/react-router/pull/15012))
- `react-router` - Mark `mask` as an optional field in `Location` for easier mocking in unit tests ([#14999](https://github.com/remix-run/react-router/pull/14999))
- `react-router` - Improve server-side route matching performance by pre-computing flattened/cached route branches ([#14967](https://github.com/remix-run/react-router/pull/14967)) ([af5d49b](https://github.com/remix-run/react-router/commit/af5d49b))
- `react-router` - Improve server-side route matching performance by pre-computing flattened/cached route branches ([#14967](https://github.com/remix-run/react-router/pull/14967))
- Performance benchmarks showed roughly a 10-15% improvement in server-side request handling performance
- `react-router` - Cache flattened/ranked route branches to optimize server-side route matching ([#14967](https://github.com/remix-run/react-router/pull/14967))
- `react-router` - Improve route matching performance in Framework/Data Mode ([#14971](https://github.com/remix-run/react-router/pull/14971)) ([af5d49b](https://github.com/remix-run/react-router/commit/af5d49b))
- `react-router` - Improve route matching performance in Framework/Data Mode ([#14971](https://github.com/remix-run/react-router/pull/14971))
- Avoiding unnecessary calls to `matchRoutes` in data router scenarios
- This includes adding back the optimization that was removed in `7.6.0` ([#13562](https://github.com/remix-run/react-router/pull/13562))
- The issues that prompted the revert have been addressed by using the available router `matches` but always updating `match.route` to the latest route in the `manifest`
Expand Down
2 changes: 2 additions & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
- goodrone
- gowthamvbhat
- GraxMonzo
- grzdev
- guppy0356
- GuptaSiddhant
- haivuw
Expand Down Expand Up @@ -225,6 +226,7 @@
- KaranRandhir
- kark
- KAROTT7
- karthik-idikuda
- kddnewton
- ken0x0a
- kentcdodds
Expand Down
2 changes: 1 addition & 1 deletion docs/api/hooks/useNavigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function SomeComponent() {
## Signature

```tsx
function useNavigation(): Navigation
function useNavigation(): Omit<Navigation, "matches" | "historyAction">
```

## Returns
Expand Down
Loading