diff --git a/.eslintignore b/.eslintignore
index dbd9643de0..f36aec4840 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -6,6 +6,17 @@ examples/**/dist/
worker-configuration.d.ts
/playground/
/playground-local/
+integration/helpers/**/dist/
+integration/helpers/**/build/
+# Temporary until we can get prettier upgraded to support `import ... with` syntax
+integration/helpers/rsc-parcel/src/server.tsx
+playwright-report/
+test-results/
+build.utils.d.ts
+.wrangler/
+.tmp/
+.react-router/
+.react-router-parcel/
packages/**/dist/
packages/react-router-dom/server.d.ts
packages/react-router-dom/server.js
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index a047864b86..4ef4cbaa8e 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -8,46 +8,31 @@ body:
value: |
Thank you for helping to improve React Router!
- - **All** bugs must have a **minimal** reproduction
- - Minimal means that it is not just pointing to a deployed site or a branch in your existing application
- - The preferred method is StackBlitz via [https://reactrouter.com/new](https://reactrouter.com/new)
- - If Stackblitz is not an option, a GitHub repo based on a fresh `create-react-router` app is acceptable
- - Only in extraordinary circumstances will code snippets or maximal reproductions be accepted
- - Issue Review
- - Issues not meeting the above criteria will be closed and pointed to this document
- - Non-issues (feature requests, usage questions) will also be closed with a link to this document
- - The SC will triage issues regularly
- - Fixing Issues
- - The SC will mark good community issues with an `Accepting PRs` label
- - These issues will generally be ones that are likely to have a small surface area fix
- - However, anyone can work on any issue but there's no guarantee the PR will be accepted if the surface area is too large for expedient review by a core team member
+ Please note that **all** bugs must have a **minimal** and **runnable** reproduction, meaning that it is not just pointing to a deployed site or a branch in your existing application, or showing a small code snippet. For more information, please refer to the [Bug/Issue Process Guidelines](https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#bugissue-process).
- ## Option 1: Submit a PR with a failing test
+ ## If you are using **Framework Mode**, you have 2 preferred options
+
+ ### Option 1 - Create a failing integration test
๐ The most helpful reproduction is to use our _bug report integration test_ template:
1. [Fork `remix-run/react-router`](https://github.com/remix-run/react-router/fork)
2. Open [`integration/bug-report-test.ts`](https://github.com/remix-run/react-router/blob/dev/integration/bug-report-test.ts) in your editor
- 3. Follow the instructions and submit a pull request with a failing bug report test!
-
- ## Option 2: Continue filling out this form
+ 3. Follow the instructions to create a failing bug report test
+ 4. Link to your forked branch with the failing test in this issue
- If you'd rather open a GitHub issue, here are other ways to share a reproduction (ordered from most helpful to least):
+ ### Option 2 - Create a **minimal** reproduction
- ๐ฅ Link to a [StackBlitz](https://reactrouter.com/new) environment
- - ๐ฅ Link to a GitHub repository
- - ๐ฅ Description of project including template, config files, `package.json` scripts, etc.
+ - ๐ฅ Link to a GitHub repository containing a minimal reproduction app
+
+ ## If you are using **Data** or **Declarative Mode**
+
+ Create a **minimal** reproduction
+
+ - ๐ฅ Link to a CodeSandbox repro: [TS](https://codesandbox.io/templates/react-vite-ts) | [JS](https://codesandbox.io/templates/react-vite)
+ - ๐ฅ Link to a GitHub repository containing a minimal reproduction app
- - type: dropdown
- id: mode
- attributes:
- label: I'm using React Router as a...
- description: See https://reactrouter.com/home for explanation
- options:
- - library
- - framework
- validations:
- required: true
- type: textarea
id: reproduction
attributes:
diff --git a/.github/workflows/close-feature-pr.yml b/.github/workflows/close-feature-pr.yml
new file mode 100644
index 0000000000..945a420f10
--- /dev/null
+++ b/.github/workflows/close-feature-pr.yml
@@ -0,0 +1,26 @@
+# Close a singular pull request that implements a feature that has not
+# gone through the Proposal process
+# Triggered by adding the `feature-request` label to an issue
+
+name: ๐ช Check Feature PR
+
+on:
+ pull_request:
+ types: [labeled]
+
+jobs:
+ close-feature-pr:
+ name: ๐ช Check Feature PR
+ if: github.repository == 'remix-run/react-router' && github.event.label.name == 'feature-request'
+ runs-on: ubuntu-latest
+ steps:
+ - name: โฌ๏ธ Checkout repo
+ uses: actions/checkout@v4
+
+ - name: ๐ช Close PR
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ gh pr comment ${{ github.event.pull_request.number }} -F ./scripts/close-feature-pr.md
+ gh pr edit ${{ github.event.pull_request.number }} --remove-label ${{ github.event.label.name }}
+ gh pr close ${{ github.event.pull_request.number }}
diff --git a/.github/workflows/close-no-repro-issue.yml b/.github/workflows/close-no-repro-issue.yml
new file mode 100644
index 0000000000..e1e9e34fa9
--- /dev/null
+++ b/.github/workflows/close-no-repro-issue.yml
@@ -0,0 +1,25 @@
+# Close a singular issue without a reproduction
+# Triggered by adding the `no-reproduction` label to an issue
+
+name: ๐ช Close issue without a reproduction
+
+on:
+ issues:
+ types: [labeled]
+
+jobs:
+ close-no-repro-issue:
+ name: ๐ช Close issue
+ if: github.repository == 'remix-run/react-router' && github.event.label.name == 'no-reproduction'
+ runs-on: ubuntu-latest
+ steps:
+ - name: โฌ๏ธ Checkout repo
+ uses: actions/checkout@v4
+
+ - name: ๐ช Close issue
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ gh issue comment ${{ github.event.issue.number }} -F ./scripts/close-no-repro-issue.md
+ gh issue edit ${{ github.event.issue.number }} --remove-label ${{ github.event.label.name }}
+ gh issue close ${{ github.event.issue.number }} -r "not planned";
diff --git a/.github/workflows/close-no-repro-issues.yml b/.github/workflows/close-no-repro-issues.yml
new file mode 100644
index 0000000000..d84bd27dd3
--- /dev/null
+++ b/.github/workflows/close-no-repro-issues.yml
@@ -0,0 +1,49 @@
+# This is a bulk-close script that was used initially to find and close issues
+# without a repro, but moving forward we'll likely use the singular version
+# (close-no-repro-issue.yml) on new issues which is driven by a label added to
+# the issue
+
+name: ๐ช Close issues without a reproduction
+
+on:
+ workflow_dispatch:
+ inputs:
+ dryRun:
+ type: boolean
+ description: "Dry Run? (no issues will be closed)"
+ default: false
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}
+
+jobs:
+ close-no-repro-issues:
+ name: ๐ช Close issues
+ if: github.repository == 'remix-run/react-router'
+ runs-on: ubuntu-latest
+ env:
+ CI: "true"
+ GH_TOKEN: ${{ github.token }}
+ steps:
+ - name: โฌ๏ธ Checkout repo
+ uses: actions/checkout@v4
+
+ - name: ๐ฆ Setup pnpm
+ uses: pnpm/action-setup@v4.1.0
+
+ - name: โ Setup node
+ uses: actions/setup-node@v4
+ with:
+ # required for --experimental-strip-types
+ node-version: 22
+ cache: "pnpm"
+
+ - name: ๐ฅ Install deps
+ run: pnpm install --frozen-lockfile
+
+ - name: ๐ช Close Issues (Dry Run)
+ if: ${{ inputs.dryRun }}
+ run: node --experimental-strip-types ./scripts/close-no-repro-issues.ts --dryRun
+
+ - name: ๐ช Close Issues
+ if: ${{ ! inputs.dryRun }}
+ run: node --experimental-strip-types ./scripts/close-no-repro-issues.ts
diff --git a/.github/workflows/deduplicate-lock-file.yml b/.github/workflows/deduplicate-lock-file.yml
index b73aa8e467..f10197ea6d 100644
--- a/.github/workflows/deduplicate-lock-file.yml
+++ b/.github/workflows/deduplicate-lock-file.yml
@@ -19,6 +19,8 @@ jobs:
steps:
- name: โฌ๏ธ Checkout repo
uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.FORMAT_PAT }}
- name: ๐ฆ Setup pnpm
uses: pnpm/action-setup@v4
@@ -44,4 +46,4 @@ jobs:
fi
git commit -m "chore: deduplicate \`pnpm-lock.yaml\`"
git push
- echo "๐ฟ https://github.com/$GITHUB_REPOSITORY/commit/$(git rev-parse HEAD)"
+ echo "๐ฟ pushed dedupe changes https://github.com/$GITHUB_REPOSITORY/commit/$(git rev-parse HEAD)"
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000000..ad63e44b8d
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,68 @@
+name: ๐ Docs
+
+on:
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: "Branch to generate docs for"
+ required: true
+ api:
+ description: "API Names to generate docs for"
+ required: false
+ default: ""
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ docs:
+ if: github.repository == 'remix-run/react-router'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: โฌ๏ธ Checkout repo
+ uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.FORMAT_PAT }}
+ ref: ${{ github.event.inputs.branch }}
+
+ - name: ๐ฆ Setup pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: โ Setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: ".nvmrc"
+ cache: pnpm
+
+ - name: ๐ฅ Install deps
+ run: pnpm install --frozen-lockfile
+
+ - name: ๐ Build
+ run: pnpm build
+
+ - name: ๐ Generate Typedoc Docs
+ run: pnpm run docs
+
+ - name: ๐ Generate Markdown Docs (for all APIs)
+ if: github.event.inputs.api == ''
+ run: node --experimental-strip-types scripts/docs.ts --path packages/react-router/lib/hooks.tsx --write
+
+ - name: ๐ Generate Markdown Docs (for specific APIs)
+ if: github.event.inputs.api != ''
+ run: node --experimental-strip-types scripts/docs.ts --path packages/react-router/lib/hooks.tsx --write --api ${{ github.event.inputs.api }}
+
+ - name: ๐ช Commit
+ run: |
+ git config --local user.email "hello@remix.run"
+ git config --local user.name "Remix Run Bot"
+
+ git add .
+ if [ -z "$(git status --porcelain)" ]; then
+ echo "๐ฟ no docs changed"
+ exit 0
+ fi
+ git commit -m "chore: generate markdown docs from jsdocs"
+ git push
+ echo "๐ฟ pushed docs changes https://github.com/$GITHUB_REPOSITORY/commit/$(git rev-parse HEAD)"
diff --git a/.github/workflows/integration-full.yml b/.github/workflows/integration-full.yml
index fd2f79bfa9..cb1d064ee2 100644
--- a/.github/workflows/integration-full.yml
+++ b/.github/workflows/integration-full.yml
@@ -40,7 +40,7 @@ jobs:
uses: ./.github/workflows/shared-integration.yml
with:
os: "windows-latest"
- node_version: "[20, 22]"
+ node_version: "[22]"
browser: '["msedge"]'
integration-macos:
diff --git a/.github/workflows/integration-pr-windows-macos.yml b/.github/workflows/integration-pr-windows-macos.yml
index a36aef144d..12bbf2b77c 100644
--- a/.github/workflows/integration-pr-windows-macos.yml
+++ b/.github/workflows/integration-pr-windows-macos.yml
@@ -32,7 +32,7 @@ jobs:
os: "windows-latest"
node_version: "[22]"
browser: '["msedge"]'
- timeout: 60
+ timeout: 120
integration-webkit:
name: "๐ Integration Test"
diff --git a/.github/workflows/release-stage-2-alpha.yml b/.github/workflows/release-stage-2-alpha.yml
new file mode 100644
index 0000000000..ea930ff35f
--- /dev/null
+++ b/.github/workflows/release-stage-2-alpha.yml
@@ -0,0 +1,86 @@
+name: ๐งช Check Alpha Release
+
+on:
+ pull_request:
+ types: [labeled]
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}
+
+env:
+ CI: true
+
+jobs:
+ alpha-release:
+ name: ๐งช Check Alpha Release
+ if: github.repository == 'remix-run/react-router' && github.event.label.name == 'alpha-release'
+ runs-on: ubuntu-latest
+ steps:
+ - name: ๐ Log Info
+ run: |
+ echo "Label: ${{ github.event.label.name }}"
+ echo "Branch: ${{ github.event.pull_request.head.ref }}"
+ echo "SHA: ${{ github.event.pull_request.head.sha }}"
+
+ - name: โฌ๏ธ Checkout repo
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: Log Git Info
+ run: |
+ git log -n 1
+ git status
+
+ - name: ๐ฆ Setup pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: โ Setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: ".nvmrc"
+ cache: "pnpm"
+
+ - name: ๐ฅ Install deps
+ run: pnpm install --frozen-lockfile
+
+ - name: โคด๏ธ Update version
+ id: version
+ run: |
+ git config --local user.email "hello@remix.run"
+ git config --local user.name "Remix Run Bot"
+ SHORT_SHA=$(git rev-parse --short HEAD)
+ NEXT_VERSION=0.0.0-experimental-${SHORT_SHA}
+ git checkout -b experimental/${NEXT_VERSION}
+ pnpm run version ${NEXT_VERSION}
+ echo "version=${NEXT_VERSION}" >> "$GITHUB_OUTPUT"
+
+ - name: ๐ Build
+ run: pnpm build
+
+ - name: ๐ Setup npm auth
+ run: |
+ echo "registry=https://registry.npmjs.org" >> ~/.npmrc
+ echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc
+
+ - name: ๐ Publish
+ run: pnpm run publish
+
+ - name: ๐ฌ Comment
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ LATEST_RELEASE_SHA=$(gh release list --limit 1 --json tagName --jq ".[0].tagName")
+ BASE_SHA=$(echo ${{ github.event.pull_request.base.sha }} | cut -c1-7)
+ COMMAND="git log --pretty=oneline ${LATEST_RELEASE_SHA}..${BASE_SHA}"
+ echo -e \
+ "[Alpha release](https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#stage-2--alpha) \
+ created: \`${{ steps.version.outputs.version }}\`\n\n \
+ โ ๏ธ **Note:** This release was created from the \`HEAD\` of this branch so it \
+ may contain commits that have landed in \`dev\` but have not been released yet \
+ depending on when this branch was created. You can run the following command \
+ to see the commits that may not have been released yet:\n\n \
+ \`\`\`bash\n \
+ ${COMMAND}\n \
+ \`\`\`" \
+ | gh pr comment ${{ github.event.pull_request.number }} --body-file -
+ gh pr edit ${{ github.event.pull_request.number }} --remove-label ${{ github.event.label.name }}
diff --git a/.gitignore b/.gitignore
index b13e7650a9..53e9a5baf1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,7 @@ node_modules/
.wireit
.eslintcache
+.parcel-cache
.tmp
tsup.config.bundled_*.mjs
build.utils.d.ts
diff --git a/.nvmrc b/.nvmrc
index 2edeafb09d..8fdd954df9 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-20
\ No newline at end of file
+22
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dfbb78e0a2..1410011e91 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,82 +13,89 @@ We manage release notes in this file instead of the paginated Github Releases Pa
Table of Contents
- [React Router Releases](#react-router-releases)
- - [v7.6.3](#v763)
+ - [v7.7.0](#v770)
+ - [What's Changed](#whats-changed)
+ - [Unstable RSC APIs](#unstable-rsc-apis)
+ - [Minor Changes](#minor-changes)
- [Patch Changes](#patch-changes)
- - [v7.6.2](#v762)
+ - [Unstable Changes](#unstable-changes)
+ - [Changes by Package](#changes-by-package)
+ - [v7.6.3](#v763)
- [Patch Changes](#patch-changes-1)
- - [v7.6.1](#v761)
+ - [v7.6.2](#v762)
- [Patch Changes](#patch-changes-2)
- - [Unstable Changes](#unstable-changes)
+ - [v7.6.1](#v761)
+ - [Patch Changes](#patch-changes-3)
+ - [Unstable Changes](#unstable-changes-1)
- [v7.6.0](#v760)
- - [What's Changed](#whats-changed)
+ - [What's Changed](#whats-changed-1)
- [`routeDiscovery` Config Option](#routediscovery-config-option)
- [Automatic Types for Future Flags](#automatic-types-for-future-flags)
- - [Minor Changes](#minor-changes)
- - [Patch Changes](#patch-changes-3)
- - [Unstable Changes](#unstable-changes-1)
- - [Changes by Package](#changes-by-package)
- - [v7.5.3](#v753)
+ - [Minor Changes](#minor-changes-1)
- [Patch Changes](#patch-changes-4)
+ - [Unstable Changes](#unstable-changes-2)
+ - [Changes by Package](#changes-by-package-1)
+ - [v7.5.3](#v753)
+ - [Patch Changes](#patch-changes-5)
- [v7.5.2](#v752)
- [Security Notice](#security-notice)
- - [Patch Changes](#patch-changes-5)
- - [v7.5.1](#v751)
- [Patch Changes](#patch-changes-6)
- - [Unstable Changes](#unstable-changes-2)
- - [v7.5.0](#v750)
- - [What's Changed](#whats-changed-1)
- - [`route.lazy` Object API](#routelazy-object-api)
- - [Minor Changes](#minor-changes-1)
+ - [v7.5.1](#v751)
- [Patch Changes](#patch-changes-7)
- [Unstable Changes](#unstable-changes-3)
- - [Changes by Package](#changes-by-package-1)
- - [v7.4.1](#v741)
- - [Security Notice](#security-notice-1)
+ - [v7.5.0](#v750)
+ - [What's Changed](#whats-changed-2)
+ - [`route.lazy` Object API](#routelazy-object-api)
+ - [Minor Changes](#minor-changes-2)
- [Patch Changes](#patch-changes-8)
- [Unstable Changes](#unstable-changes-4)
- - [v7.4.0](#v740)
- - [Minor Changes](#minor-changes-2)
+ - [Changes by Package](#changes-by-package-2)
+ - [v7.4.1](#v741)
+ - [Security Notice](#security-notice-1)
- [Patch Changes](#patch-changes-9)
- [Unstable Changes](#unstable-changes-5)
- - [Changes by Package](#changes-by-package-2)
- - [v7.3.0](#v730)
+ - [v7.4.0](#v740)
- [Minor Changes](#minor-changes-3)
- [Patch Changes](#patch-changes-10)
- [Unstable Changes](#unstable-changes-6)
+ - [Changes by Package](#changes-by-package-3)
+ - [v7.3.0](#v730)
+ - [Minor Changes](#minor-changes-4)
+ - [Patch Changes](#patch-changes-11)
+ - [Unstable Changes](#unstable-changes-7)
- [Client-side `context` (unstable)](#client-side-context-unstable)
- [Middleware (unstable)](#middleware-unstable)
- [Middleware `context` parameter](#middleware-context-parameter)
- [`unstable_SerializesTo`](#unstable_serializesto)
- - [Changes by Package](#changes-by-package-3)
+ - [Changes by Package](#changes-by-package-4)
- [v7.2.0](#v720)
- - [What's Changed](#whats-changed-2)
+ - [What's Changed](#whats-changed-3)
- [Type-safe `href` utility](#type-safe-href-utility)
- [Prerendering with a SPA Fallback](#prerendering-with-a-spa-fallback)
- [Allow a root `loader` in SPA Mode](#allow-a-root-loader-in-spa-mode)
- - [Minor Changes](#minor-changes-4)
- - [Patch Changes](#patch-changes-11)
- - [Unstable Changes](#unstable-changes-7)
+ - [Minor Changes](#minor-changes-5)
+ - [Patch Changes](#patch-changes-12)
+ - [Unstable Changes](#unstable-changes-8)
- [Split Route Modules (unstable)](#split-route-modules-unstable)
- - [Changes by Package](#changes-by-package-4)
+ - [Changes by Package](#changes-by-package-5)
- [v7.1.5](#v715)
- - [Patch Changes](#patch-changes-12)
- - [v7.1.4](#v714)
- [Patch Changes](#patch-changes-13)
- - [v7.1.3](#v713)
+ - [v7.1.4](#v714)
- [Patch Changes](#patch-changes-14)
- - [v7.1.2](#v712)
+ - [v7.1.3](#v713)
- [Patch Changes](#patch-changes-15)
- - [v7.1.1](#v711)
+ - [v7.1.2](#v712)
- [Patch Changes](#patch-changes-16)
- - [v7.1.0](#v710)
- - [Minor Changes](#minor-changes-5)
+ - [v7.1.1](#v711)
- [Patch Changes](#patch-changes-17)
- - [Changes by Package](#changes-by-package-5)
- - [v7.0.2](#v702)
+ - [v7.1.0](#v710)
+ - [Minor Changes](#minor-changes-6)
- [Patch Changes](#patch-changes-18)
- - [v7.0.1](#v701)
+ - [Changes by Package](#changes-by-package-6)
+ - [v7.0.2](#v702)
- [Patch Changes](#patch-changes-19)
+ - [v7.0.1](#v701)
+ - [Patch Changes](#patch-changes-20)
- [v7.0.0](#v700)
- [Breaking Changes](#breaking-changes)
- [Package Restructuring](#package-restructuring)
@@ -104,202 +111,202 @@ We manage release notes in this file instead of the paginated Github Releases Pa
- [Prerendering](#prerendering)
- [Major Changes (`react-router`)](#major-changes-react-router)
- [Major Changes (`@react-router/*`)](#major-changes-react-router-1)
- - [Minor Changes](#minor-changes-6)
- - [Patch Changes](#patch-changes-20)
- - [Changes by Package](#changes-by-package-6)
+ - [Minor Changes](#minor-changes-7)
+ - [Patch Changes](#patch-changes-21)
+ - [Changes by Package](#changes-by-package-7)
- [React Router v6 Releases](#react-router-v6-releases)
- [v6.30.1](#v6301)
- - [Patch Changes](#patch-changes-21)
- - [v6.30.0](#v6300)
- - [Minor Changes](#minor-changes-7)
- [Patch Changes](#patch-changes-22)
- - [v6.29.0](#v6290)
+ - [v6.30.0](#v6300)
- [Minor Changes](#minor-changes-8)
- [Patch Changes](#patch-changes-23)
- - [v6.28.2](#v6282)
+ - [v6.29.0](#v6290)
+ - [Minor Changes](#minor-changes-9)
- [Patch Changes](#patch-changes-24)
- - [v6.28.1](#v6281)
+ - [v6.28.2](#v6282)
- [Patch Changes](#patch-changes-25)
- - [v6.28.0](#v6280)
- - [What's Changed](#whats-changed-3)
- - [Minor Changes](#minor-changes-9)
+ - [v6.28.1](#v6281)
- [Patch Changes](#patch-changes-26)
- - [v6.27.0](#v6270)
+ - [v6.28.0](#v6280)
- [What's Changed](#whats-changed-4)
- - [Stabilized APIs](#stabilized-apis)
- [Minor Changes](#minor-changes-10)
- [Patch Changes](#patch-changes-27)
- - [v6.26.2](#v6262)
+ - [v6.27.0](#v6270)
+ - [What's Changed](#whats-changed-5)
+ - [Stabilized APIs](#stabilized-apis)
+ - [Minor Changes](#minor-changes-11)
- [Patch Changes](#patch-changes-28)
- - [v6.26.1](#v6261)
+ - [v6.26.2](#v6262)
- [Patch Changes](#patch-changes-29)
- - [v6.26.0](#v6260)
- - [Minor Changes](#minor-changes-11)
+ - [v6.26.1](#v6261)
- [Patch Changes](#patch-changes-30)
- - [v6.25.1](#v6251)
+ - [v6.26.0](#v6260)
+ - [Minor Changes](#minor-changes-12)
- [Patch Changes](#patch-changes-31)
+ - [v6.25.1](#v6251)
+ - [Patch Changes](#patch-changes-32)
- [v6.25.0](#v6250)
- - [What's Changed](#whats-changed-5)
+ - [What's Changed](#whats-changed-6)
- [Stabilized `v7_skipActionErrorRevalidation`](#stabilized-v7_skipactionerrorrevalidation)
- - [Minor Changes](#minor-changes-12)
- - [Patch Changes](#patch-changes-32)
- - [v6.24.1](#v6241)
+ - [Minor Changes](#minor-changes-13)
- [Patch Changes](#patch-changes-33)
+ - [v6.24.1](#v6241)
+ - [Patch Changes](#patch-changes-34)
- [v6.24.0](#v6240)
- - [What's Changed](#whats-changed-6)
+ - [What's Changed](#whats-changed-7)
- [Lazy Route Discovery (a.k.a. "Fog of War")](#lazy-route-discovery-aka-fog-of-war)
- - [Minor Changes](#minor-changes-13)
- - [Patch Changes](#patch-changes-34)
- - [v6.23.1](#v6231)
+ - [Minor Changes](#minor-changes-14)
- [Patch Changes](#patch-changes-35)
+ - [v6.23.1](#v6231)
+ - [Patch Changes](#patch-changes-36)
- [v6.23.0](#v6230)
- - [What's Changed](#whats-changed-7)
+ - [What's Changed](#whats-changed-8)
- [Data Strategy (unstable)](#data-strategy-unstable)
- [Skip Action Error Revalidation (unstable)](#skip-action-error-revalidation-unstable)
- - [Minor Changes](#minor-changes-14)
+ - [Minor Changes](#minor-changes-15)
- [v6.22.3](#v6223)
- - [Patch Changes](#patch-changes-36)
- - [v6.22.2](#v6222)
- [Patch Changes](#patch-changes-37)
- - [v6.22.1](#v6221)
+ - [v6.22.2](#v6222)
- [Patch Changes](#patch-changes-38)
+ - [v6.22.1](#v6221)
+ - [Patch Changes](#patch-changes-39)
- [v6.22.0](#v6220)
- - [What's Changed](#whats-changed-8)
+ - [What's Changed](#whats-changed-9)
- [Core Web Vitals Technology Report Flag](#core-web-vitals-technology-report-flag)
- - [Minor Changes](#minor-changes-15)
- - [Patch Changes](#patch-changes-39)
- - [v6.21.3](#v6213)
+ - [Minor Changes](#minor-changes-16)
- [Patch Changes](#patch-changes-40)
- - [v6.21.2](#v6212)
+ - [v6.21.3](#v6213)
- [Patch Changes](#patch-changes-41)
- - [v6.21.1](#v6211)
+ - [v6.21.2](#v6212)
- [Patch Changes](#patch-changes-42)
+ - [v6.21.1](#v6211)
+ - [Patch Changes](#patch-changes-43)
- [v6.21.0](#v6210)
- - [What's Changed](#whats-changed-9)
+ - [What's Changed](#whats-changed-10)
- [`future.v7_relativeSplatPath`](#futurev7_relativesplatpath)
- [Partial Hydration](#partial-hydration)
- - [Minor Changes](#minor-changes-16)
- - [Patch Changes](#patch-changes-43)
- - [v6.20.1](#v6201)
- - [Patch Changes](#patch-changes-44)
- - [v6.20.0](#v6200)
- [Minor Changes](#minor-changes-17)
+ - [Patch Changes](#patch-changes-44)
+ - [v6.20.1](#v6201)
- [Patch Changes](#patch-changes-45)
- - [v6.19.0](#v6190)
- - [What's Changed](#whats-changed-10)
- - [`unstable_flushSync` API](#unstable_flushsync-api)
+ - [v6.20.0](#v6200)
- [Minor Changes](#minor-changes-18)
- [Patch Changes](#patch-changes-46)
- - [v6.18.0](#v6180)
+ - [v6.19.0](#v6190)
- [What's Changed](#whats-changed-11)
- - [New Fetcher APIs](#new-fetcher-apis)
- - [Persistence Future Flag (`future.v7_fetcherPersist`)](#persistence-future-flag-futurev7_fetcherpersist)
+ - [`unstable_flushSync` API](#unstable_flushsync-api)
- [Minor Changes](#minor-changes-19)
- [Patch Changes](#patch-changes-47)
- - [v6.17.0](#v6170)
+ - [v6.18.0](#v6180)
- [What's Changed](#whats-changed-12)
- - [View Transitions ๐](#view-transitions-)
+ - [New Fetcher APIs](#new-fetcher-apis)
+ - [Persistence Future Flag (`future.v7_fetcherPersist`)](#persistence-future-flag-futurev7_fetcherpersist)
- [Minor Changes](#minor-changes-20)
- [Patch Changes](#patch-changes-48)
- - [v6.16.0](#v6160)
+ - [v6.17.0](#v6170)
+ - [What's Changed](#whats-changed-13)
+ - [View Transitions ๐](#view-transitions-)
- [Minor Changes](#minor-changes-21)
- [Patch Changes](#patch-changes-49)
- - [v6.15.0](#v6150)
+ - [v6.16.0](#v6160)
- [Minor Changes](#minor-changes-22)
- [Patch Changes](#patch-changes-50)
- - [v6.14.2](#v6142)
+ - [v6.15.0](#v6150)
+ - [Minor Changes](#minor-changes-23)
- [Patch Changes](#patch-changes-51)
- - [v6.14.1](#v6141)
+ - [v6.14.2](#v6142)
- [Patch Changes](#patch-changes-52)
- - [v6.14.0](#v6140)
- - [What's Changed](#whats-changed-13)
- - [JSON/Text Submissions](#jsontext-submissions)
- - [Minor Changes](#minor-changes-23)
+ - [v6.14.1](#v6141)
- [Patch Changes](#patch-changes-53)
- - [v6.13.0](#v6130)
+ - [v6.14.0](#v6140)
- [What's Changed](#whats-changed-14)
- - [`future.v7_startTransition`](#futurev7_starttransition)
+ - [JSON/Text Submissions](#jsontext-submissions)
- [Minor Changes](#minor-changes-24)
- [Patch Changes](#patch-changes-54)
- - [v6.12.1](#v6121)
- - [Patch Changes](#patch-changes-55)
- - [v6.12.0](#v6120)
+ - [v6.13.0](#v6130)
- [What's Changed](#whats-changed-15)
- - [`React.startTransition` support](#reactstarttransition-support)
+ - [`future.v7_startTransition`](#futurev7_starttransition)
- [Minor Changes](#minor-changes-25)
+ - [Patch Changes](#patch-changes-55)
+ - [v6.12.1](#v6121)
- [Patch Changes](#patch-changes-56)
- - [v6.11.2](#v6112)
+ - [v6.12.0](#v6120)
+ - [What's Changed](#whats-changed-16)
+ - [`React.startTransition` support](#reactstarttransition-support)
+ - [Minor Changes](#minor-changes-26)
- [Patch Changes](#patch-changes-57)
- - [v6.11.1](#v6111)
+ - [v6.11.2](#v6112)
- [Patch Changes](#patch-changes-58)
- - [v6.11.0](#v6110)
- - [Minor Changes](#minor-changes-26)
+ - [v6.11.1](#v6111)
- [Patch Changes](#patch-changes-59)
- - [v6.10.0](#v6100)
- - [What's Changed](#whats-changed-16)
+ - [v6.11.0](#v6110)
- [Minor Changes](#minor-changes-27)
- - [`future.v7_normalizeFormMethod`](#futurev7_normalizeformmethod)
- [Patch Changes](#patch-changes-60)
- - [v6.9.0](#v690)
+ - [v6.10.0](#v6100)
- [What's Changed](#whats-changed-17)
- - [`Component`/`ErrorBoundary` route properties](#componenterrorboundary-route-properties)
- - [Introducing Lazy Route Modules](#introducing-lazy-route-modules)
- [Minor Changes](#minor-changes-28)
+ - [`future.v7_normalizeFormMethod`](#futurev7_normalizeformmethod)
- [Patch Changes](#patch-changes-61)
- - [v6.8.2](#v682)
+ - [v6.9.0](#v690)
+ - [What's Changed](#whats-changed-18)
+ - [`Component`/`ErrorBoundary` route properties](#componenterrorboundary-route-properties)
+ - [Introducing Lazy Route Modules](#introducing-lazy-route-modules)
+ - [Minor Changes](#minor-changes-29)
- [Patch Changes](#patch-changes-62)
- - [v6.8.1](#v681)
+ - [v6.8.2](#v682)
- [Patch Changes](#patch-changes-63)
- - [v6.8.0](#v680)
- - [Minor Changes](#minor-changes-29)
+ - [v6.8.1](#v681)
- [Patch Changes](#patch-changes-64)
- - [v6.7.0](#v670)
+ - [v6.8.0](#v680)
- [Minor Changes](#minor-changes-30)
- [Patch Changes](#patch-changes-65)
- - [v6.6.2](#v662)
+ - [v6.7.0](#v670)
+ - [Minor Changes](#minor-changes-31)
- [Patch Changes](#patch-changes-66)
- - [v6.6.1](#v661)
+ - [v6.6.2](#v662)
- [Patch Changes](#patch-changes-67)
- - [v6.6.0](#v660)
- - [What's Changed](#whats-changed-18)
- - [Minor Changes](#minor-changes-31)
+ - [v6.6.1](#v661)
- [Patch Changes](#patch-changes-68)
- - [v6.5.0](#v650)
+ - [v6.6.0](#v660)
- [What's Changed](#whats-changed-19)
- [Minor Changes](#minor-changes-32)
- [Patch Changes](#patch-changes-69)
- - [v6.4.5](#v645)
+ - [v6.5.0](#v650)
+ - [What's Changed](#whats-changed-20)
+ - [Minor Changes](#minor-changes-33)
- [Patch Changes](#patch-changes-70)
- - [v6.4.4](#v644)
+ - [v6.4.5](#v645)
- [Patch Changes](#patch-changes-71)
- - [v6.4.3](#v643)
+ - [v6.4.4](#v644)
- [Patch Changes](#patch-changes-72)
- - [v6.4.2](#v642)
+ - [v6.4.3](#v643)
- [Patch Changes](#patch-changes-73)
- - [v6.4.1](#v641)
+ - [v6.4.2](#v642)
- [Patch Changes](#patch-changes-74)
+ - [v6.4.1](#v641)
+ - [Patch Changes](#patch-changes-75)
- [v6.4.0](#v640)
- - [What's Changed](#whats-changed-20)
+ - [What's Changed](#whats-changed-21)
- [Remix Data APIs](#remix-data-apis)
- - [Patch Changes](#patch-changes-75)
+ - [Patch Changes](#patch-changes-76)
- [v6.3.0](#v630)
- - [Minor Changes](#minor-changes-33)
+ - [Minor Changes](#minor-changes-34)
- [v6.2.2](#v622)
- - [Patch Changes](#patch-changes-76)
- - [v6.2.1](#v621)
- [Patch Changes](#patch-changes-77)
- - [v6.2.0](#v620)
- - [Minor Changes](#minor-changes-34)
+ - [v6.2.1](#v621)
- [Patch Changes](#patch-changes-78)
- - [v6.1.1](#v611)
- - [Patch Changes](#patch-changes-79)
- - [v6.1.0](#v610)
+ - [v6.2.0](#v620)
- [Minor Changes](#minor-changes-35)
+ - [Patch Changes](#patch-changes-79)
+ - [v6.1.1](#v611)
- [Patch Changes](#patch-changes-80)
- - [v6.0.2](#v602)
+ - [v6.1.0](#v610)
+ - [Minor Changes](#minor-changes-36)
- [Patch Changes](#patch-changes-81)
- - [v6.0.1](#v601)
+ - [v6.0.2](#v602)
- [Patch Changes](#patch-changes-82)
+ - [v6.0.1](#v601)
+ - [Patch Changes](#patch-changes-83)
- [v6.0.0](#v600)
@@ -341,6 +348,93 @@ Date: YYYY-MM-DD
**Full Changelog**: [`v7.X.Y...v7.X.Y`](https://github.com/remix-run/react-router/compare/react-router@7.X.Y...react-router@7.X.Y)
-->
+## v7.7.0
+
+Date: 2025-07-16
+
+### What's Changed
+
+#### Unstable RSC APIs
+
+We're excited to introduce experimental support for RSC in Data Mode via the following new APIs:
+
+- [`unstable_RSCHydratedRouter`](https://reactrouter.com/api/rsc/RSCHydratedRouter)
+- [`unstable_RSCStaticRouter`](https://reactrouter.com/api/rsc/RSCStaticRouter)
+- [`unstable_createCallServer`](https://reactrouter.com/api/rsc/createCallServer)
+- [`unstable_getRSCStream`](https://reactrouter.com/api/rsc/getRSCStream)
+- [`unstable_matchRSCServerRequest`](https://reactrouter.com/api/rsc/matchRSCServerRequest)
+- [`unstable_routeRSCServerRequest`](https://reactrouter.com/api/rsc/routeRSCServerRequest)
+
+For more information, check out the [blog post](https://remix.run/blog/react-router-and-react-server-components) and the [RSC Docs](https://reactrouter.com/how-to/react-server-components).
+
+### Minor Changes
+
+- `create-react-router` - Add Deno as a supported and detectable package manager. Note that this detection will only work with Deno versions 2.0.5 and above. If you are using an older version version of Deno then you must specify the --package-manager CLI flag set to `deno`. ([#12327](https://github.com/remix-run/react-router/pull/12327))
+- `@react-router/remix-config-routes-adapter` - Export `DefineRouteFunction` type alongside `DefineRoutesFunction` ([#13945](https://github.com/remix-run/react-router/pull/13945))
+
+### Patch Changes
+
+- `react-router` - Handle `InvalidCharacterError` when validating cookie signature ([#13847](https://github.com/remix-run/react-router/pull/13847))
+- `react-router` - Pass a copy of `searchParams` to the `setSearchParams` callback function to avoid mutations of the internal `searchParams` instance ([#12784](https://github.com/remix-run/react-router/pull/12784))
+ - This causes bugs if you mutate the current stateful `searchParams` when a navigation is blocked because the internal instance gets out of sync with `useLocation().search`
+- `react-router` - Support invalid `Date` in `turbo-stream` v2 fork ([#13684](https://github.com/remix-run/react-router/pull/13684))
+- `react-router` - In Framework Mode, clear critical CSS in development after initial render ([#13872](https://github.com/remix-run/react-router/pull/13872), [#13995](https://github.com/remix-run/react-router/pull/13995))
+- `react-router` - Strip search parameters from `patchRoutesOnNavigation` `path` param for fetcher calls ([#13911](https://github.com/remix-run/react-router/pull/13911))
+- `react-router` - Skip scroll restoration on `useRevalidator()` calls because they're not new locations ([#13671](https://github.com/remix-run/react-router/pull/13671))
+- `react-router` - Support unencoded UTF-8 routes in prerender config with `ssr` set to `false` ([#13699](https://github.com/remix-run/react-router/pull/13699))
+- `react-router` - Do not throw if the url hash is not a valid URI component ([#13247](https://github.com/remix-run/react-router/pull/13247))
+- `react-router` - Remove `Content-Length` header from Single Fetch responses ([#13902](https://github.com/remix-run/react-router/pull/13902))
+- `react-router` - Fix a regression in `createRoutesStub` introduced with the middleware feature ([#13946](https://github.com/remix-run/react-router/pull/13946))
+
+ - As part of that work we altered the signature to align with the new middleware APIs without making it backwards compatible with the prior `AppLoadContext` API
+ - This permitted `createRoutesStub` to work if you were opting into middleware and the updated `context` typings, but broke `createRoutesStub` for users not yet opting into middleware
+ - We've reverted this change and re-implemented it in such a way that both sets of users can leverage it
+ - โ ๏ธ This may be a breaking bug for if you have adopted the unstable Middleware feature and are using `createRoutesStub` with the updated API.
+
+ ```tsx
+ // If you have not opted into middleware, the old API should work again
+ let context: AppLoadContext = {
+ /*...*/
+ };
+ let Stub = createRoutesStub(routes, context);
+
+ // If you have opted into middleware, you should now pass an instantiated
+ // `unstable_routerContextProvider` instead of a `getContext` factory function.
+ let context = new unstable_RouterContextProvider();
+ context.set(SomeContext, someValue);
+ let Stub = createRoutesStub(routes, context);
+ ```
+
+- `@react-router/dev` - Update `vite-node` to `^3.2.2` to support Vite 7 ([#13781](https://github.com/remix-run/react-router/pull/13781))
+- `@react-router/dev` - Properly handle `https` protocol in dev mode ([#13746](https://github.com/remix-run/react-router/pull/13746))
+- `@react-router/dev` - Fix missing styles when Vite's `build.cssCodeSplit` option is disabled ([#13943](https://github.com/remix-run/react-router/pull/13943))
+- `@react-router/dev` - Allow `.mts` and `.mjs` extensions for route config file ([#13931](https://github.com/remix-run/react-router/pull/13931))
+- `@react-router/dev` - Fix prerender file locations when `cwd` differs from project root ([#13824](https://github.com/remix-run/react-router/pull/13824))
+- `@react-router/dev` - Improve chunk error logging when a chunk cannot be found during the build ([#13799](https://github.com/remix-run/react-router/pull/13799))
+- `@react-router/dev` - Fix incorrectly configured `externalConditions` which had enabled `module` condition for externals and broke builds with certain packages (like Emotion) ([#13871](https://github.com/remix-run/react-router/pull/13871))
+
+### Unstable Changes
+
+โ ๏ธ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_
+
+- Add unstable RSC support ([#13700](https://github.com/remix-run/react-router/pull/13700))
+ - For more information, see the [RSC documentation](https://reactrouter.com/start/rsc/installation)
+
+### Changes by Package
+
+- [`create-react-router`](https://github.com/remix-run/react-router/blob/react-router%407.7.0/packages/create-react-router/CHANGELOG.md#770)
+- [`react-router`](https://github.com/remix-run/react-router/blob/react-router%407.7.0/packages/react-router/CHANGELOG.md#770)
+- [`@react-router/architect`](https://github.com/remix-run/react-router/blob/react-router%407.7.0/packages/react-router-architect/CHANGELOG.md#770)
+- [`@react-router/cloudflare`](https://github.com/remix-run/react-router/blob/react-router%407.7.0/packages/react-router-cloudflare/CHANGELOG.md#770)
+- [`@react-router/dev`](https://github.com/remix-run/react-router/blob/react-router%407.7.0/packages/react-router-dev/CHANGELOG.md#770)
+- [`@react-router/express`](https://github.com/remix-run/react-router/blob/react-router%407.7.0/packages/react-router-express/CHANGELOG.md#770)
+- [`@react-router/fs-routes`](https://github.com/remix-run/react-router/blob/react-router%407.7.0/packages/react-router-fs-routes/CHANGELOG.md#770)
+- [`@react-router/node`](https://github.com/remix-run/react-router/blob/react-router%407.7.0/packages/react-router-node/CHANGELOG.md#770)
+- [`@react-router/remix-config-routes-adapter`](https://github.com/remix-run/react-router/blob/react-router%407.7.0/packages/react-router-remix-config-routes-adapter/CHANGELOG.md#770)
+- [`@react-router/serve`](https://github.com/remix-run/react-router/blob/react-router%407.7.0/packages/react-router-serve/CHANGELOG.md#770)
+
+**Full Changelog**: [`v7.6.3...v7.7.0`](https://github.com/remix-run/react-router/compare/react-router@7.6.3...react-router@7.7.0)
+
## v7.6.3
Date: 2025-06-27
diff --git a/GOVERNANCE.md b/GOVERNANCE.md
index 519e54c0c6..a64a9f99c3 100644
--- a/GOVERNANCE.md
+++ b/GOVERNANCE.md
@@ -28,7 +28,7 @@ The following design goals should be considered when considering RFCs for accept
- **Less is More**. React Router has gained a _lot_ of functionality in the past years, but with that comes a bunch of new API surface. It's time to hone in on the core functionality and aim to reduce API surface _without sacrificing capabilities_. This may come in multiple forms, such as condensing a few existing APIs into a singular API, or deprecating current APIs in favor of a new React API.
- **Routing and Data Focused.** Focus on core router-integrated/router-centric APIs and avoid adding first-class APIs that can be implemented in user-land
-- **Simple Migration Paths.** Major version upgrade's don't have to stink. Breaking changes should be implemented behind future flags. Deprecations should be properly marked ahead of time in code and in documentation. Console warnings should be added prior to major releases to nudge developers towards the changes they can begin to make to prep for the upgrade.
+- **Simple Migration Paths.** Major version upgrades don't have to stink. Breaking changes should be implemented behind future flags. Deprecations should be properly marked ahead of time in code and in documentation. Console warnings should be added prior to major releases to nudge developers towards the changes they can begin to make to prep for the upgrade.
- **Lowest Common Mode.** Features are added at the lowest mode possible (`declarative -> data -> framework`) and then leveraged by the higher-level modes. This ensures that the largest number of React Router applications can leverage them.
- **Regular Release Cadence**. Aim for major SemVer releases on a ~yearly basis so application developers can prepare in advance.
@@ -38,11 +38,11 @@ The Steering Committee will be in charge of accepting RFC's for consideration, a
The SC will initially consist of the Remix team developers:
-- Matt Brophy
-- Pedro Cattori
-- Mark Dalgleish
-- Jacob Ebey
-- Brooks Lybrand
+- Matt Brophy ([`@brophdawg11`](https://github.com/brophdawg11))
+- Pedro Cattori ([`@pcattori`](https://github.com/pcattori))
+- Mark Dalgleish ([`@markdalgleish`](https://github.com/markdalgleish))
+- Jacob Ebey ([`@jacob-ebey`](https://github.com/jacob-ebey))
+- Brooks Lybrand ([`@brookslybrand`](https://github.com/brookslybrand))
In the future, we may add a limited number of heavily involved community members to the SC as well.
@@ -50,12 +50,15 @@ To reduce friction, the SC will primarily operate asynchronously via GitHub, but
## Bug/Issue Process
-Due to the # 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.
+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.
-- **All** bugs must have a **minimal** reproduction [^3]
- - Minimal means that it is not just pointing to a deployed site or a branch in your existing application
- - The preferred method is StackBlitz via [https://reactrouter.com/new](https://reactrouter.com/new)
- - If Stackblitz is not an option, a GitHub repo based on a fresh `create-react-router` app is acceptable
+- **All** bugs must have a **minimal** and **runnable** reproduction [^3]
+ - _Minimal_ means that it is not just pointing to a deployed site or a branch in your existing application
+ - _Runnable_ means that it is a working application where we can see the issue, not just a few snippets of code that need to be manually reassembled into a running application
+ - The preferred methods for reproductions are:
+ - **Framework Mode**: [StackBlitz](https://reactrouter.com/new) or a GitHub fork with a failing integration test based on [`bug-report-test.ts`](integration/bug-report-test.ts)
+ - **Data/Declarative Modes**: [CodeSandbox (TS)](https://codesandbox.io/templates/react-vite-ts) or [CodeSandbox (JS)](https://codesandbox.io/templates/react-vite)
+ - If StackBlitz/CodeSandbox is not an option, a GitHub repo based on a fresh `npx create-react-router` app is acceptable
- Only in extraordinary circumstances will code snippets or maximal reproductions be accepted
- Issue Review
- Issues not meeting the above criteria will be closed and pointed to this document
@@ -70,16 +73,18 @@ Due to the # of React Router applications out there, we have to be a bit strict
## New Feature Process
-The process for new features being added to React Router will follow a series of stages loosely based on the [TC39 Process](https://tc39.es/process-document/). 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. 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.
+The process for new features being added to React Router will follow a series of stages loosely based on the [TC39 Process](https://tc39.es/process-document/). 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. 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.
-| 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 or more 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. This should be accompanied by a mechanism for community members to test the feature via [pkg.pr.new](https://pkg.pr.new/), [patch-package](https://www.npmjs.com/package/patch-package), or [pnpm patch](https://pnpm.io/cli/patch) | 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 at least 50% of the 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. |
+Once a feature reaches Stage 2, it will be added to the [Roadmap](https://github.com/orgs/remix-run/projects/5) 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 or more 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 at least 50% of the 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. |
To get a feature accepted and implemented in React Router, it will go through the following stages:
@@ -106,9 +111,11 @@ To get a feature accepted and implemented in React Router, it will go through th
### Stage 2 โ Alpha
- A proposal enters **Stage 2 โ Alpha** once a PR has been opened implementing the feature in an `unstable_` state
-- At this stage, we are looking for early community testing _before_ merging any work to the React Router repo โ so these PRs should provide a mechanism for community members to opt-into to alpha testing
- - Ideally, we'll be able to wire up [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) on the repo
- - Otherwise, a `.patch` file (or instructions to create one) can be included in the PR for use with [patch-package](https://www.npmjs.com/package/patch-package) or [pnpm patch](https://pnpm.io/cli/patch)
+- At this stage, we should open an Issue for the Proposal and add it to the [Roadmap](https://github.com/orgs/remix-run/projects/5)
+- At this stage, we are looking for early community testing _before_ merging any work to the React Router repo โ so these PRs should provide a mechanism for community members to opt into to alpha testing
+ - Maintainers can trigger an alpha release from the PR branch by adding the `alpha-release` label, which will kick off an experimental release and comment it back on the PR
+ - Because the alpha release may contain other work committed to `dev` but not yet released in a stable version, it may not be ideal for testing in all cases
+ - In these cases, PR authors may also add the contents for a `.patch` file in a comment that folks can use via [patch-package](https://www.npmjs.com/package/patch-package) or [pnpm patch](https://pnpm.io/cli/patch)
- Feedback from alpha testers is considered essential for further progress
- The PR should also contain a changeset documenting the new API for the release notes
- SC members will review and approve the PR via GitHub reviews
@@ -137,5 +144,3 @@ To get a feature accepted and implemented in React Router, it will go through th
- A proposal enters **Stage 5 โ Stable** once it receives **Stage 4 โ Stabilization** PR approvals from at least 50% of the SC members and is merged to `dev`
- This will include the stable feature in `nightly` releases and the next normal SemVer release
-
-[mermaid]: https://mermaid.live/edit#pako:eNqVkm9r2zAQxr_KcTDYwAn-E8eOGYXGScdeFEYzGLQuQ4nPjsCWjCx3beN89yn2rJKX06tDd8_vnpPuhAeZEyZYVPLP4ciUhp-bTIA5t0_fc2LPMJvd9L8U1wQ_lGxky6oe1k8Z7jQrCdyve3Xzecp8yfB5VK8H3XQPrGmUfDFBoWQNPuxSuKd6T6rtIbUwD2aQStHynBTTXAqLS0fcA_C6qagmobkogbXQiVazfUW_e9hYjm84t1VzZFa_mfTXRpiGilirIXQ_gSyubG0tLjC4NekP2naiKarly8WJtQGNooK_9nBn5QsjN9GeV_z9eqq7_3f1zWLDf9iKDA8drEnVjOfmL08Xeob6aB4qw8SEORWsq3SGmTib0q7JmaZtzrVUmBSsaslB1mm5exMHTLTqaCracFYqVtuqhglMTviKySqYh4toFUfxIo6DyPUdfMPEW0ZzNwjNvb9wYz8Ko7OD71IagjuPo1UQL1dLb7F0PS8IB9zjkBx70mDpflzJYTMdVLIrj7Z_qS4jjtWKhFmUVHZCYxKe_wLxVeMU
diff --git a/contributors.yml b/contributors.yml
index a07fbc81fc..55a5cc202a 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -18,6 +18,7 @@
- akamfoad
- alany411
- alberto
+- AlemTuzlak
- Aleuck
- alexandernanberg
- alexanderson1993
@@ -26,6 +27,7 @@
- amitdahan
- AmRo045
- amsal
+- Andarist
- andreasottosson-polestar
- andreiduca
- antonmontrezor
@@ -50,6 +52,7 @@
- BDomzalski
- bhbs
- bilalk711
+- bmsuseluda
- bobziroll
- bravo-kernel
- Brendonovich
@@ -89,10 +92,12 @@
- david-crespo
- davidbielik
- dcblair
+- dchenk
- decadentsavant
- developit
- dgrijuela
- DigitalNaut
+- DimaAmega
- dmitrytarassov
- dokeet
- doytch
@@ -111,6 +116,7 @@
- FilipJirsak
- focusotter
- foxscotch
+- frodi-karlsson
- frontsideair
- fucancode
- fyzhu
@@ -133,9 +139,11 @@
- harshmangalam
- HenriqueLimas
- hernanif1
+- hi-ogawa
- HK-SHAO
- holynewbie
- hongji00
+- hoosierhuy
- hsbtr
- hyesungoh
- iamnishanth
@@ -190,6 +198,7 @@
- ken0x0a
- kentcdodds
- kettanaito
+- kigawas
- kilavvy
- kiliman
- kkirsche
@@ -249,6 +258,7 @@
- mjackson
- mlewando
- mm-jpoole
+- mobregozo
- modex98
- morleytatro
- ms10596
@@ -267,6 +277,7 @@
- nowells
- Nurai1
- Obi-Dann
+- okalil
- OlegDev1
- omahs
- omar-moquete
@@ -290,7 +301,9 @@
- pruszel
- pwdcd
- pyitphyoaung
+- redabacha
- refusado
+- remorses
- renyu-io
- reyronald
- rifaidev
@@ -303,12 +316,14 @@
- rtzll
- rubeonline
- ruidi-huang
+- rururux
- ryanflorence
- ryanhiebert
- saengmotmi
- samimsu
- sanjai451
- sanketshah19
+- sapphi-red
- saul-atomrigs
- sbolel
- scarf005
@@ -354,6 +369,7 @@
- ThornWu
- tiborbarsi
- timdorr
+- timfisher
- TkDodo
- tkindy
- tlinhart
@@ -367,6 +383,7 @@
- tosinamuda
- triangularcube
- trungpv1601
+- tryonelove
- TrySound
- ttys026
- Tumas2
@@ -377,6 +394,7 @@
- ValiantCat
- vdusart
- vesan
+- vezaynk
- VictorElHajj
- vijaypushkin
- vikingviolinist
@@ -400,7 +418,9 @@
- yionr
- yracnet
- ytori
+- yuhwan-park
- yuleicul
+- yuri-poliantsev
- zeevick10
- zeromask1337
- zheng-chuang
diff --git a/docs/api/components/Await.md b/docs/api/components/Await.md
index c74977022d..1d45df6513 100644
--- a/docs/api/components/Await.md
+++ b/docs/api/components/Await.md
@@ -15,7 +15,7 @@ Used to render promise values with automatic error handling.
```tsx
import { Await, useLoaderData } from "react-router";
-export function loader() {
+export async function loader() {
// not awaited
const reviews = getReviews();
// awaited (blocks the transition)
diff --git a/docs/api/components/ServerRouter.md b/docs/api/components/ServerRouter.md
index 813988fff1..bb33c85e0d 100644
--- a/docs/api/components/ServerRouter.md
+++ b/docs/api/components/ServerRouter.md
@@ -10,7 +10,7 @@ title: ServerRouter
[Reference Documentation โ](https://api.reactrouter.com/v7/functions/react_router.ServerRouter.html)
-Rendered at the top of the app in a custom entry.server.tsx.
+Rendered at the top of the app in a custom [entry.server.tsx][entry-server].
## Props
@@ -31,3 +31,5 @@ _No documentation_
[modes: framework]
_No documentation_
+
+[entry-server]: ../framework-conventions/entry.server.tsx
diff --git a/docs/api/data-routers/RouterProvider.md b/docs/api/data-routers/RouterProvider.md
index 6d1f9c4714..34b01ed74b 100644
--- a/docs/api/data-routers/RouterProvider.md
+++ b/docs/api/data-routers/RouterProvider.md
@@ -10,16 +10,16 @@ title: RouterProvider
[Reference Documentation โ](https://api.reactrouter.com/v7/functions/react_router.RouterProvider.html)
-Initializes a data router, subscribes to its changes, and renders the
+Accepts a data router, subscribes to its changes, and renders the
matching components. Should typically be at the top of an app's element tree.
```tsx
-import {
- RouterProvider,
- createBrowserRouter,
-} from "react-router";
import { createRoot } from "react-dom/client";
-let router = createBrowserRouter();
+import { createBrowserRouter } from "react-router";
+import { RouterProvider } from "react-router/dom";
+
+let router = createBrowserRouter(routes);
+
createRoot(document.getElementById("root")).render(
);
@@ -31,10 +31,15 @@ createRoot(document.getElementById("root")).render(
[modes: data]
-_No documentation_
+This is an implementation detail and shouldn't need to be used in your application.
+
+This prop provides a way to inject the `react-dom` `flushSync` implementation when running `RouterProvider` in a DOM environment for use during routing operations with `flushSync` enabled (i.e., [useNavigate](../hooks/useNavigate#signature)).
+
+- If you're running `RouterProvider` in a memory environment (such as unit tests) you can import it from `react-router` and omit this prop
+- If you are running `RouterProvider` in a DOM environment, you should be importing it from `react-router/dom` which automatically passes the `react-dom` `flushSync` implementation for you
### router
[modes: data]
-_No documentation_
+The initialized data router for the application.
diff --git a/docs/api/data-routers/index.md b/docs/api/data-routers/index.md
index 4c08fcb58a..eddd554727 100644
--- a/docs/api/data-routers/index.md
+++ b/docs/api/data-routers/index.md
@@ -1,4 +1,4 @@
---
title: Data Routers
+order: 4
---
-
diff --git a/docs/api/declarative-routers/index.md b/docs/api/declarative-routers/index.md
index ef2329fd66..f5edcdf78a 100644
--- a/docs/api/declarative-routers/index.md
+++ b/docs/api/declarative-routers/index.md
@@ -1,4 +1,4 @@
---
title: Declarative Routers
+order: 5
---
-
diff --git a/docs/api/framework-conventions/client-modules.md b/docs/api/framework-conventions/client-modules.md
new file mode 100644
index 0000000000..5b0bed6f2c
--- /dev/null
+++ b/docs/api/framework-conventions/client-modules.md
@@ -0,0 +1,114 @@
+---
+title: .client modules
+---
+
+# `.client` modules
+
+[MODES: framework]
+
+## Summary
+
+You may have a file or dependency that uses module side effects in the browser. You can use `*.client.ts` on file names or nest files within `.client` directories to force them out of server bundles.
+
+```ts filename=feature-check.client.ts
+// this would break the server
+export const supportsVibrationAPI =
+ "vibrate" in window.navigator;
+```
+
+Note that values exported from this module will all be `undefined` on the server, so the only places to use them are in [`useEffect`][use_effect] and user events like click handlers.
+
+```ts
+import { supportsVibrationAPI } from "./feature-check.client.ts";
+
+console.log(supportsVibrationAPI);
+// server: undefined
+// client: true | false
+```
+
+## Usage Patterns
+
+### Individual Files
+
+Mark individual files as client-only by adding `.client` to the filename:
+
+```txt
+app/
+โโโ utils.client.ts ๐ client-only file
+โโโ feature-detection.client.ts
+โโโ root.tsx
+```
+
+### Client Directories
+
+Mark entire directories as client-only by using `.client` in the directory name:
+
+```txt
+app/
+โโโ .client/ ๐ entire directory is client-only
+โ โโโ analytics.ts
+โ โโโ feature-detection.ts
+โ โโโ browser-utils.ts
+โโโ components/
+โโโ root.tsx
+```
+
+## Examples
+
+### Browser Feature Detection
+
+```ts filename=app/utils/browser.client.ts
+export const canUseDOM = typeof window !== "undefined";
+
+export const hasWebGL = !!window.WebGLRenderingContext;
+
+export const supportsVibrationAPI =
+ "vibrate" in window.navigator;
+```
+
+### Client-Only Libraries
+
+```ts filename=app/analytics.client.ts
+// This would break on the server
+import { track } from "some-browser-only-analytics-lib";
+
+export function trackEvent(eventName: string, data: any) {
+ track(eventName, data);
+}
+```
+
+### Using Client Modules
+
+```tsx filename=app/routes/dashboard.tsx
+import { useEffect } from "react";
+import {
+ canUseDOM,
+ supportsLocalStorage,
+ supportsVibrationAPI,
+} from "../utils/browser.client.ts";
+import { trackEvent } from "../analytics.client.ts";
+
+export default function Dashboard() {
+ useEffect(() => {
+ // These values are undefined on the server
+ if (canUseDOM && supportsVibrationAPI) {
+ console.log("Device supports vibration");
+ }
+
+ // Safe localStorage usage
+ const savedTheme =
+ supportsLocalStorage.getItem("theme");
+ if (savedTheme) {
+ document.body.className = savedTheme;
+ }
+
+ trackEvent("dashboard_viewed", {
+ timestamp: Date.now(),
+ });
+ }, []);
+
+ return
Dashboard
;
+}
+```
+
+[use_effect]: https://react.dev/reference/react/useEffect
diff --git a/docs/api/framework-conventions/entry.client.tsx.md b/docs/api/framework-conventions/entry.client.tsx.md
new file mode 100644
index 0000000000..57adcbd9c0
--- /dev/null
+++ b/docs/api/framework-conventions/entry.client.tsx.md
@@ -0,0 +1,43 @@
+---
+title: entry.client.tsx
+order: 4
+---
+
+# entry.client.tsx
+
+[MODES: framework]
+
+## Summary
+
+
+This file is optional
+
+
+This file is the entry point for the browser and is responsible for hydrating the markup generated by the server in your [server entry module][server-entry]
+
+This is the first piece of code that runs in the browser. You can initialize any other client-side code here, such as client side libraries, add client only providers, etc.
+
+```tsx filename=app/entry.client.tsx
+import { startTransition, StrictMode } from "react";
+import { hydrateRoot } from "react-dom/client";
+import { HydratedRouter } from "react-router/dom";
+
+startTransition(() => {
+ hydrateRoot(
+ document,
+
+
+
+ );
+});
+```
+
+## Generating `entry.client.tsx`
+
+By default, React Router will handle hydrating your app on the client for you. You can reveal the default entry client file with the following:
+
+```shellscript nonumber
+npx react-router reveal
+```
+
+[server-entry]: ./entry.server.tsx
diff --git a/docs/api/framework-conventions/entry.server.tsx.md b/docs/api/framework-conventions/entry.server.tsx.md
new file mode 100644
index 0000000000..657f77b49b
--- /dev/null
+++ b/docs/api/framework-conventions/entry.server.tsx.md
@@ -0,0 +1,162 @@
+---
+title: entry.server.tsx
+order: 5
+---
+
+# entry.server.tsx
+
+[MODES: framework]
+
+## Summary
+
+
+This file is optional
+
+
+This file is the server-side entry point that controls how your React Router application generates HTTP responses on the server.
+
+This module should render the markup for the current page using a [``][serverrouter] element with the `context` and `url` for the current request. This markup will (optionally) be re-hydrated once JavaScript loads in the browser using the [client entry module][client-entry].
+
+## Generating `entry.server.tsx`
+
+By default, React Router will handle generating the HTTP Response for you. You can reveal the default entry server file with the following:
+
+```shellscript nonumber
+npx react-router reveal
+```
+
+## Exports
+
+### `default`
+
+The `default` export of this module is a function that lets you create the response, including HTTP status, headers, and HTML, giving you full control over the way the markup is generated and sent to the client.
+
+```tsx filename=app/entry.server.tsx
+import { PassThrough } from "node:stream";
+import type { EntryContext } from "react-router";
+import { createReadableStreamFromReadable } from "@react-router/node";
+import { ServerRouter } from "react-router";
+import { renderToPipeableStream } from "react-dom/server";
+
+export default function handleRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ routerContext: EntryContext
+) {
+ return new Promise((resolve, reject) => {
+ const { pipe, abort } = renderToPipeableStream(
+ ,
+ {
+ onShellReady() {
+ responseHeaders.set("Content-Type", "text/html");
+
+ const body = new PassThrough();
+ const stream =
+ createReadableStreamFromReadable(body);
+
+ resolve(
+ new Response(stream, {
+ headers: responseHeaders,
+ status: responseStatusCode,
+ })
+ );
+
+ pipe(body);
+ },
+ onShellError(error: unknown) {
+ reject(error);
+ },
+ }
+ );
+ });
+}
+```
+
+### `streamTimeout`
+
+If you are [streaming] responses, you can export an optional `streamTimeout` value (in milliseconds) that will control the amount of time the server will wait for streamed promises to settle before rejecting outstanding promises and closing the stream.
+
+It's recommended to decouple this value from the timeout in which you abort the React renderer. You should always set the React rendering timeout to a higher value so it has time to stream down the underlying rejections from your `streamTimeout`.
+
+```tsx lines=[1-2,13-15]
+// Reject all pending promises from handler functions after 10 seconds
+export const streamTimeout = 10000;
+
+export default function handleRequest(...) {
+ return new Promise((resolve, reject) => {
+ // ...
+
+ const { pipe, abort } = renderToPipeableStream(
+ ,
+ { /* ... */ }
+ );
+
+ // Abort the streaming render pass after 11 seconds to allow the rejected
+ // boundaries to be flushed
+ setTimeout(abort, streamTimeout + 1000);
+ });
+}
+```
+
+### `handleDataRequest`
+
+You can export an optional `handleDataRequest` function that will allow you to modify the response of a data request. These are the requests that do not render HTML, but rather return the `loader` and `action` data to the browser once client-side hydration has occurred.
+
+```tsx
+export function handleDataRequest(
+ response: Response,
+ {
+ request,
+ params,
+ context,
+ }: LoaderFunctionArgs | ActionFunctionArgs
+) {
+ response.headers.set("X-Custom-Header", "value");
+ return response;
+}
+```
+
+### `handleError`
+
+By default, React Router will log encountered server-side errors to the console. If you'd like more control over the logging, or would like to also report these errors to an external service, then you can export an optional `handleError` function which will give you control (and will disable the built-in error logging).
+
+```tsx
+export function handleError(
+ error: unknown,
+ {
+ request,
+ params,
+ context,
+ }: LoaderFunctionArgs | ActionFunctionArgs
+) {
+ if (!request.signal.aborted) {
+ sendErrorToErrorReportingService(error);
+ console.error(formatErrorForJsonLogging(error));
+ }
+}
+```
+
+_Note that you generally want to avoid logging when the request was aborted, since React Router's cancellation and race-condition handling can cause a lot of requests to be aborted._
+
+**Streaming Rendering Errors**
+
+When you are streaming your HTML responses via [`renderToPipeableStream`][rendertopipeablestream] or [`renderToReadableStream`][rendertoreadablestream], your own `handleError` implementation will only handle errors encountered during the initial shell render. If you encounter a rendering error during subsequent streamed rendering you will need to handle these errors manually since the React Router server has already sent the Response by that point.
+
+For `renderToPipeableStream`, you can handle these errors in the `onError` callback function. You will need to toggle a boolean in `onShellReady` so you know if the error was a shell rendering error (and can be ignored) or an async
+
+For an example, please refer to the default [`entry.server.tsx`][node-streaming-entry-server] for Node.
+
+**Thrown Responses**
+
+Note that this does not handle thrown `Response` instances from your `loader`/`action` functions. The intention of this handler is to find bugs in your code which result in unexpected thrown errors. If you are detecting a scenario and throwing a 401/404/etc. `Response` in your `loader`/`action` then it's an expected flow that is handled by your code. If you also wish to log, or send those to an external service, that should be done at the time you throw the response.
+
+[client-entry]: ./entry.client.tsx
+[serverrouter]: ../components/ServerRouter
+[streaming]: ../how-to/suspense
+[rendertopipeablestream]: https://react.dev/reference/react-dom/server/renderToPipeableStream
+[rendertoreadablestream]: https://react.dev/reference/react-dom/server/renderToReadableStream
+[node-streaming-entry-server]: https://github.com/remix-run/react-router/blob/dev/packages/react-router-dev/config/defaults/entry.server.node.tsx
diff --git a/docs/api/framework-conventions/index.md b/docs/api/framework-conventions/index.md
new file mode 100644
index 0000000000..33a5ff320f
--- /dev/null
+++ b/docs/api/framework-conventions/index.md
@@ -0,0 +1,4 @@
+---
+title: Framework Conventions
+order: 3
+---
diff --git a/docs/api/framework-conventions/react-router.config.ts.md b/docs/api/framework-conventions/react-router.config.ts.md
new file mode 100644
index 0000000000..fcb4038240
--- /dev/null
+++ b/docs/api/framework-conventions/react-router.config.ts.md
@@ -0,0 +1,210 @@
+---
+title: react-router.config.ts
+order: 3
+---
+
+# react-router.config.ts
+
+[MODES: framework]
+
+## Summary
+
+
+This file is optional
+
+
+[Reference Documentation โ](https://api.reactrouter.com/v7/types/_react_router_dev.config.Config.html)
+
+React Router framework configuration file that lets you customize aspects of your React Router application like server-side rendering, directory locations, and build settings.
+
+```tsx filename=react-router.config.ts
+import type { Config } from "@react-router/dev/config";
+
+export default {
+ appDirectory: "app",
+ buildDirectory: "build",
+ ssr: true,
+ prerender: ["/", "/about"],
+} satisfies Config;
+```
+
+## Options
+
+### `appDirectory`
+
+The path to the `app` directory, relative to the root directory. Defaults to `"app"`.
+
+```tsx filename=react-router.config.ts
+export default {
+ appDirectory: "src",
+} satisfies Config;
+```
+
+### `basename`
+
+The React Router app basename. Defaults to `"/"`.
+
+```tsx filename=react-router.config.ts
+export default {
+ basename: "/my-app",
+} satisfies Config;
+```
+
+### `buildDirectory`
+
+The path to the build directory, relative to the project. Defaults to `"build"`.
+
+```tsx filename=react-router.config.ts
+export default {
+ buildDirectory: "dist",
+} satisfies Config;
+```
+
+### `buildEnd`
+
+A function that is called after the full React Router build is complete.
+
+```tsx filename=react-router.config.ts
+export default {
+ buildEnd: async ({ buildManifest, serverBuildPath }) => {
+ // Custom build logic here
+ console.log("Build completed!");
+ },
+} satisfies Config;
+```
+
+### `future`
+
+Enabled future flags for opting into upcoming features.
+
+See [Future Flags][future-flags] for more information.
+
+```tsx filename=react-router.config.ts
+export default {
+ future: {
+ // Enable future flags here
+ },
+} satisfies Config;
+```
+
+### `prerender`
+
+An array of URLs to prerender to HTML files at build time. Can also be a function returning an array to dynamically generate URLs.
+
+See [Pre-Rendering][pre-rendering] for more information.
+
+```tsx filename=react-router.config.ts
+export default {
+ // Static array
+ prerender: ["/", "/about", "/contact"],
+
+ // Or dynamic function
+ prerender: async ({ getStaticPaths }) => {
+ const paths = await getStaticPaths();
+ return ["/", ...paths];
+ },
+} satisfies Config;
+```
+
+### `presets`
+
+An array of React Router plugin config presets to ease integration with other platforms and tools.
+
+See [Presets][presets] for more information.
+
+```tsx filename=react-router.config.ts
+export default {
+ presets: [
+ // Add presets here
+ ],
+} satisfies Config;
+```
+
+### `routeDiscovery`
+
+Configure how routes are discovered and loaded by the client. Defaults to `mode: "lazy"` with `manifestPath: "/__manifest"`.
+
+**Options:**
+
+- `mode: "lazy"` - Routes are discovered as the user navigates (default)
+ - `manifestPath` - Custom path for manifest requests when using `lazy` mode
+- `mode: "initial"` - All routes are included in the initial manifest
+
+```tsx filename=react-router.config.ts
+export default {
+ // Enable lazy route discovery (default)
+ routeDiscovery: {
+ mode: "lazy",
+ manifestPath: "/__manifest",
+ },
+
+ // Use a custom manifest path
+ routeDiscovery: {
+ mode: "lazy",
+ manifestPath: "/custom-manifest",
+ },
+
+ // Disable lazy discovery and include all routes initially
+ routeDiscovery: { mode: "initial" },
+} satisfies Config;
+```
+
+See [Lazy Route Discovery][lazy-route-discovery] for more information.
+
+### `serverBuildFile`
+
+The file name of the server build output. This file should end in a `.js` extension and should be deployed to your server. Defaults to `"index.js"`.
+
+```tsx filename=react-router.config.ts
+export default {
+ serverBuildFile: "server.js",
+} satisfies Config;
+```
+
+### `serverBundles`
+
+A function for assigning routes to different server bundles. This function should return a server bundle ID which will be used as the bundle's directory name within the server build directory.
+
+See [Server Bundles][server-bundles] for more information.
+
+```tsx filename=react-router.config.ts
+export default {
+ serverBundles: ({ branch }) => {
+ // Return bundle ID based on route branch
+ return branch.some((route) => route.id === "admin")
+ ? "admin"
+ : "main";
+ },
+} satisfies Config;
+```
+
+### `serverModuleFormat`
+
+The output format of the server build. Defaults to `"esm"`.
+
+```tsx filename=react-router.config.ts
+export default {
+ serverModuleFormat: "cjs", // or "esm"
+} satisfies Config;
+```
+
+### `ssr`
+
+If `true`, React Router will server render your application.
+
+If `false`, React Router will pre-render your application and save it as an `index.html` file with your assets so your application can be deployed as a SPA without server-rendering. See ["SPA Mode"][spa-mode] for more information.
+
+Defaults to `true`.
+
+```tsx filename=react-router.config.ts
+export default {
+ ssr: false, // disabled server-side rendering
+} satisfies Config;
+```
+
+[future-flags]: ../../upgrading/future
+[presets]: ../../how-to/presets
+[server-bundles]: ../../how-to/server-bundles
+[pre-rendering]: ../../how-to/pre-rendering
+[spa-mode]: ../../how-to/spa
+[lazy-route-discovery]: ../../explanation/lazy-route-discovery
diff --git a/docs/api/framework-conventions/root.tsx.md b/docs/api/framework-conventions/root.tsx.md
new file mode 100644
index 0000000000..b9822d7b9c
--- /dev/null
+++ b/docs/api/framework-conventions/root.tsx.md
@@ -0,0 +1,199 @@
+---
+title: root.tsx
+order: 1
+---
+
+# root.tsx
+
+[MODES: framework]
+
+## Summary
+
+
+This file is required
+
+
+The "root" route (`app/root.tsx`) is the only _required_ route in your React Router application because it is the parent to all routes and is in charge of rendering the root `` document.
+
+```tsx filename=app/root.tsx
+import { Outlet, Scripts } from "react-router";
+
+import "./global-styles.css";
+
+export default function App() {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+```
+
+## Components to Render
+
+Because the root route manages your document, it is the proper place to render a handful of "document-level" components React Router provides. These components are to be used once inside your root route and they include everything React Router figured out or built in order for your page to render properly.
+
+```tsx filename=app/root.tsx
+import {
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+} from "react-router";
+
+export default function App() {
+ return (
+
+
+
+
+
+
+ {/* Child routes render here */}
+
+
+ {/* Manages scroll position for client-side transitions */}
+ {/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
+
+
+ {/* Script tags go here */}
+ {/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
+
+
+
+ );
+}
+```
+
+If you are are not on React 19 or choosing not to use React's [``][react-link], [``][react-title], and [``][react-meta] components, and instead relying on React Router's [`links`][react-router-links] and [`meta`][react-router-meta] exports, you need to add the following to your root route:
+
+```tsx filename=app/root.tsx
+import { Links, Meta } from "react-router";
+
+export default function App() {
+ return (
+
+
+ {/* All `meta` exports on all routes will render here */}
+
+
+ {/* All `link` exports on all routes will render here */}
+
+
+
+
+
+
+
+
+ );
+}
+```
+
+## Layout Export
+
+The root route supports all [route module exports][route-module].
+
+The root route also supports an additional optional `Layout` export. The `Layout` component serves 2 purposes:
+
+1. Avoid duplicating your document's "app shell" across your root component, `HydrateFallback`, and `ErrorBoundary`
+2. Prevent React from re-mounting your app shell elements when switching between the root component/`HydrateFallback`/`ErrorBoundary` which can cause a FOUC if React removes and re-adds `` tags from your `` component.
+
+`Layout` takes a single `children` prop, which is the `default` export (e.g. `App`), `HydrateFallback`, or `ErrorBoundary`.
+
+```tsx filename=app/root.tsx
+export function Layout({ children }) {
+ return (
+
+
+
+
+
+
+
+
+ {/* children will be the root Component, ErrorBoundary, or HydrateFallback */}
+ {children}
+
+
+
+
+ );
+}
+
+export default function App() {
+ return ;
+}
+
+export function ErrorBoundary() {}
+```
+
+**A note on `useLoaderData`in the `Layout` Component**
+
+`useLoaderData` is not permitted to be used in `ErrorBoundary` components because it is intended for the happy-path route rendering, and its typings have a built-in assumption that the `loader` ran successfully and returned something. That assumption doesn't hold in an `ErrorBoundary` because it could have been the `loader` that threw and triggered the boundary! In order to access loader data in `ErrorBoundary`'s, you can use `useRouteLoaderData` which accounts for the loader data potentially being `undefined`.
+
+Because your `Layout` component is used in both success and error flows, this same restriction holds. If you need to fork logic in your `Layout` depending on if it was a successful request or not, you can use `useRouteLoaderData("root")` and `useRouteError()`.
+
+Because your `` component is used for rendering the `ErrorBoundary`, you should be _very defensive_ to ensure that you can render your `ErrorBoundary` without encountering any render errors. If your `Layout` throws another error trying to render the boundary, then it can't be used and your UI will fall back to the very minimal built-in default `ErrorBoundary`.
+
+```tsx filename=app/root.tsx lines=[6-7,19-29,32-34]
+export function Layout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const data = useRouteLoaderData("root");
+ const error = useRouteError();
+
+ return (
+
+
+
+
+
+
+
+
+
+ {data ? (
+
+ ) : null}
+ {children}
+
+
+
+
+ );
+}
+```
+
+[route-module]: ../start/framework/route-module
+[react-link]: https://react.dev/reference/react-dom/components/link
+[react-meta]: https://react.dev/reference/react-dom/components/meta
+[react-title]: https://react.dev/reference/react-dom/components/title
+[react-router-links]: ../../start/framework/route-module#links
+[react-router-meta]: ../../start/framework/route-module#meta
diff --git a/docs/api/framework-conventions/routes.ts.md b/docs/api/framework-conventions/routes.ts.md
new file mode 100644
index 0000000000..7d4845c42d
--- /dev/null
+++ b/docs/api/framework-conventions/routes.ts.md
@@ -0,0 +1,67 @@
+---
+title: routes.ts
+order: 2
+---
+
+# routes.ts
+
+[MODES: framework]
+
+## Summary
+
+
+This file is required
+
+
+[Reference Documentation โ](https://api.reactrouter.com/v7/interfaces/_react_router_dev.routes.RouteConfigEntry.html)
+
+Configuration file that maps URL patterns to route modules in your application.
+
+See the [routing guide][routing] for more information.
+
+## Examples
+
+### Basic
+
+Configure your routes as an array of objects.
+
+```tsx filename=app/routes.ts
+import {
+ type RouteConfig,
+ route,
+} from "@react-router/dev/routes";
+
+export default [
+ route("some/path", "./some/file.tsx"),
+ // pattern ^ ^ module file
+] satisfies RouteConfig;
+```
+
+You can use the following helpers to create route config entries:
+
+- [`route`][route] โ Helper function for creating a route config entry
+- [`index`][index] โ Helper function for creating a route config entry for an index route
+- [`layout`][layout] โ Helper function for creating a route config entry for a layout route
+- [`prefix`][prefix] โ Helper function for adding a path prefix to a set of routes without needing to introduce a parent route file
+- [`relative`][relative] โ Creates a set of route config helpers that resolve file paths relative to the given directory. Designed to support splitting route config into multiple files within different directories
+
+### File-based Routing
+
+If you prefer to define your routes via file naming conventions rather than configuration, the `@react-router/fs-routes` package provides a [file system routing convention][file-route-conventions]:
+
+```ts filename=app/routes.ts
+import { type RouteConfig } from "@react-router/dev/routes";
+import { flatRoutes } from "@react-router/fs-routes";
+
+export default flatRoutes() satisfies RouteConfig;
+```
+
+### Route Helpers
+
+[routing]: ../../start/framework/routing
+[route]: https://api.reactrouter.com/v7/functions/_react_router_dev.routes.route.html
+[index]: https://api.reactrouter.com/v7/functions/_react_router_dev.routes.index.html
+[layout]: https://api.reactrouter.com/v7/functions/_react_router_dev.routes.layout.html
+[prefix]: https://api.reactrouter.com/v7/functions/_react_router_dev.routes.prefix.html
+[relative]: https://api.reactrouter.com/v7/functions/_react_router_dev.routes.relative.html
+[file-route-conventions]: ../../how-to/file-route-conventions
diff --git a/docs/api/framework-conventions/server-modules.md b/docs/api/framework-conventions/server-modules.md
new file mode 100644
index 0000000000..0bb842bf29
--- /dev/null
+++ b/docs/api/framework-conventions/server-modules.md
@@ -0,0 +1,145 @@
+---
+title: .server modules
+---
+
+# `.server` modules
+
+[MODES: framework]
+
+## Summary
+
+Server-only modules that are excluded from client bundles and only run on the server.
+
+```ts filename=auth.server.ts
+// This would expose secrets on the client
+export const JWT_SECRET = process.env.JWT_SECRET;
+
+export function validateToken(token: string) {
+ // Server-only authentication logic
+}
+```
+
+`.server` modules are a good way to explicitly mark entire modules as server-only. The build will fail if any code in a `.server` file or `.server` directory accidentally ends up in the client module graph.
+
+## Usage Patterns
+
+### Individual Files
+
+Mark individual files as server-only by adding `.server` to the filename:
+
+```txt
+app/
+โโโ auth.server.ts ๐ server-only file
+โโโ database.server.ts
+โโโ email.server.ts
+โโโ root.tsx
+```
+
+### Server Directories
+
+Mark entire directories as server-only by using `.server` in the directory name:
+
+```txt
+app/
+โโโ .server/ ๐ entire directory is server-only
+โ โโโ auth.ts
+โ โโโ database.ts
+โ โโโ email.ts
+โโโ components/
+โโโ root.tsx
+```
+
+## Examples
+
+### Database Connection
+
+```ts filename=app/utils/db.server.ts
+import { PrismaClient } from "@prisma/client";
+
+// This would expose database credentials on the client
+const db = new PrismaClient({
+ datasources: {
+ db: {
+ url: process.env.DATABASE_URL,
+ },
+ },
+});
+
+export { db };
+```
+
+### Authentication Utilities
+
+```ts filename=app/utils/auth.server.ts
+import jwt from "jsonwebtoken";
+import bcrypt from "bcryptjs";
+
+const JWT_SECRET = process.env.JWT_SECRET!;
+
+export function hashPassword(password: string) {
+ return bcrypt.hash(password, 10);
+}
+
+export function verifyPassword(
+ password: string,
+ hash: string
+) {
+ return bcrypt.compare(password, hash);
+}
+
+export function createToken(userId: string) {
+ return jwt.sign({ userId }, JWT_SECRET, {
+ expiresIn: "7d",
+ });
+}
+
+export function verifyToken(token: string) {
+ return jwt.verify(token, JWT_SECRET) as {
+ userId: string;
+ };
+}
+```
+
+### Using Server Modules
+
+```tsx filename=app/routes/login.tsx
+import type { ActionFunctionArgs } from "react-router";
+import { redirect } from "react-router";
+import {
+ hashPassword,
+ createToken,
+} from "../utils/auth.server";
+import { db } from "../utils/db.server";
+
+export async function action({
+ request,
+}: ActionFunctionArgs) {
+ const formData = await request.formData();
+ const email = formData.get("email") as string;
+ const password = formData.get("password") as string;
+
+ // Server-only operations
+ const hashedPassword = await hashPassword(password);
+ const user = await db.user.create({
+ data: { email, password: hashedPassword },
+ });
+
+ const token = createToken(user.id);
+
+ return redirect("/dashboard", {
+ headers: {
+ "Set-Cookie": `token=${token}; HttpOnly; Secure; SameSite=Strict`,
+ },
+ });
+}
+
+export default function Login() {
+ return (
+
+ );
+}
+```
diff --git a/docs/api/hooks/useLocation.md b/docs/api/hooks/useLocation.md
index 5072be97b8..ffc2e260a1 100644
--- a/docs/api/hooks/useLocation.md
+++ b/docs/api/hooks/useLocation.md
@@ -10,7 +10,7 @@ title: useLocation
[Reference Documentation โ](https://api.reactrouter.com/v7/functions/react_router.useLocation.html)
-Returns the current [Location]([../Other/Location](https://api.reactrouter.com/v7/interfaces/react_router.Location.html)). This can be useful if you'd like to perform some side effect whenever it changes.
+Returns the current [Location](https://api.reactrouter.com/v7/interfaces/react_router.Location.html). This can be useful if you'd like to perform some side effect whenever it changes.
```tsx
import * as React from 'react'
diff --git a/docs/api/hooks/unstable_usePrompt.md b/docs/api/hooks/usePrompt.md
similarity index 97%
rename from docs/api/hooks/unstable_usePrompt.md
rename to docs/api/hooks/usePrompt.md
index 6238a0205e..48bc6ebc0a 100644
--- a/docs/api/hooks/unstable_usePrompt.md
+++ b/docs/api/hooks/usePrompt.md
@@ -1,5 +1,6 @@
---
-title: unstable_usePrompt
+title: usePrompt
+unstable: true
---
# unstable_usePrompt
diff --git a/docs/api/rsc/RSCHydratedRouter.md b/docs/api/rsc/RSCHydratedRouter.md
new file mode 100644
index 0000000000..9b7d6de832
--- /dev/null
+++ b/docs/api/rsc/RSCHydratedRouter.md
@@ -0,0 +1,53 @@
+---
+title: RSCHydratedRouter
+unstable: true
+---
+
+# RSCHydratedRouter
+
+[MODES: data]
+
+## Summary
+
+Hydrates a server rendered `RSCPayload` in the browser.
+
+```tsx filename=entry.browser.tsx lines=[7-12]
+createFromReadableStream(getRSCStream()).then(
+ (payload: RSCServerPayload) => {
+ startTransition(async () => {
+ hydrateRoot(
+ document,
+
+
+ ,
+ {
+ formState: await getFormState(payload),
+ }
+ );
+ });
+ }
+);
+```
+
+## Props
+
+### createFromReadableStream
+
+Your `react-server-dom-xyz/client`'s `createFromReadableStream` function, used to decode payloads from the server.
+
+### payload
+
+The decoded `RSCPayload` to hydrate.
+
+### routeDiscovery
+
+`eager` or `lazy` - Determines if links are eagerly discovered, or delayed until clicked.
+
+### unstable_getContext
+
+A function that returns an `unstable_InitialContext` object (`Map`), for use in client loaders, actions and middleware.
diff --git a/docs/api/rsc/RSCStaticRouter.md b/docs/api/rsc/RSCStaticRouter.md
new file mode 100644
index 0000000000..67d8876f66
--- /dev/null
+++ b/docs/api/rsc/RSCStaticRouter.md
@@ -0,0 +1,37 @@
+---
+title: RSCStaticRouter
+unstable: true
+---
+
+# RSCStaticRouter
+
+[MODES: data]
+
+## Summary
+
+Pre-renders an `RSCPayload` to HTML. Usually used in `routeRSCServerRequest`'s `renderHTML` callback.
+
+```tsx filename=entry.ssr.tsx lines=[9]
+routeRSCServerRequest({
+ request,
+ fetchServer,
+ createFromReadableStream,
+ async renderHTML(getPayload) {
+ const payload = await getPayload();
+
+ return await renderHTMLToReadableStream(
+ ,
+ {
+ bootstrapScriptContent,
+ formState: await getFormState(payload),
+ }
+ );
+ },
+});
+```
+
+## Props
+
+### getPayload
+
+A function that starts decoding of the `RSCPayload`. Usually passed through from `routeRSCServerRequest`'s `renderHTML`.
diff --git a/docs/api/rsc/createCallServer.md b/docs/api/rsc/createCallServer.md
new file mode 100644
index 0000000000..5cbe511448
--- /dev/null
+++ b/docs/api/rsc/createCallServer.md
@@ -0,0 +1,32 @@
+---
+title: createCallServer
+unstable: true
+---
+
+# createCallServer
+
+[MODES: data]
+
+## Summary
+
+Create a React `callServer` implementation for React Router.
+
+```tsx filename=entry.browser.tsx
+setServerCallback(
+ createCallServer({
+ createFromReadableStream,
+ createTemporaryReferenceSet,
+ encodeReply,
+ })
+);
+```
+
+## Options
+
+### createFromReadableStream
+
+Your `react-server-dom-xyz/client`'s `createFromReadableStream`. Used to decode payloads from the server.
+
+### encodeReply
+
+Your `react-server-dom-xyz/client`'s `encodeReply`. Used when sending payloads to the server.
diff --git a/docs/api/rsc/getRSCStream.md b/docs/api/rsc/getRSCStream.md
new file mode 100644
index 0000000000..8840e7b0a2
--- /dev/null
+++ b/docs/api/rsc/getRSCStream.md
@@ -0,0 +1,30 @@
+---
+title: getRSCStream
+unstable: true
+---
+
+# getRSCStream
+
+[MODES: data]
+
+## Summary
+
+Get the prerendered RSC stream for hydration. Usually passed directly to your `react-server-dom-xyz/client`'s `createFromReadableStream`.
+
+```tsx filename=entry.browser.tsx lines=[1]
+createFromReadableStream(getRSCStream()).then(
+ (payload: RSCServerPayload) => {
+ startTransition(async () => {
+ hydrateRoot(
+ document,
+
+
+ ,
+ {
+ /* ... */
+ }
+ );
+ });
+ }
+);
+```
diff --git a/docs/api/rsc/index.md b/docs/api/rsc/index.md
new file mode 100644
index 0000000000..e8159c0f44
--- /dev/null
+++ b/docs/api/rsc/index.md
@@ -0,0 +1,3 @@
+---
+title: RSC (Unstable)
+---
diff --git a/docs/api/rsc/matchRSCServerRequest.md b/docs/api/rsc/matchRSCServerRequest.md
new file mode 100644
index 0000000000..247a0242b8
--- /dev/null
+++ b/docs/api/rsc/matchRSCServerRequest.md
@@ -0,0 +1,71 @@
+---
+title: matchRSCServerRequest
+unstable: true
+---
+
+# matchRSCServerRequest
+
+[MODES: data]
+
+## Summary
+
+Matches the given routes to a Request and returns a RSC Response encoding an `RSCPayload` for consumption by a RSC enabled client router.
+
+```tsx filename=entry.rsc.ts
+matchRSCServerRequest({
+ createTemporaryReferenceSet,
+ decodeAction,
+ decodeFormState,
+ decodeReply,
+ loadServerAction,
+ request,
+ routes: routes(),
+ generateResponse(match) {
+ return new Response(
+ renderToReadableStream(match.payload),
+ {
+ status: match.statusCode,
+ headers: match.headers,
+ }
+ );
+ },
+});
+```
+
+## Options
+
+### basename
+
+The basename to use when matching the request.
+
+### decodeAction
+
+Your `react-server-dom-xyz/server`'s `decodeAction` function, responsible for loading a server action.
+
+### decodeReply
+
+Your `react-server-dom-xyz/server`'s `decodeReply` function, used to decode the server function's arguments and bind them to the implementation for invocation by the router.
+
+### decodeFormState
+
+A function responsible for decoding form state for progressively enhanceable forms with `useActionState` using your `react-server-dom-xyz/server`'s `decodeFormState`.
+
+### generateResponse
+
+A function responsible for using your `renderToReadableStream` to generate a Response encoding the `RSCPayload`.
+
+### loadServerAction
+
+Your `react-server-dom-xyz/server`'s `loadServerAction` function, used to load a server action by ID.
+
+### request
+
+The request to match against.
+
+### requestContext
+
+An instance of `unstable_RouterContextProvider` that should be created per request, to be passed to loaders, actions and middleware.
+
+### routes
+
+Your route definitions.
diff --git a/docs/api/rsc/routeRSCServerRequest.md b/docs/api/rsc/routeRSCServerRequest.md
new file mode 100644
index 0000000000..84d5030c9b
--- /dev/null
+++ b/docs/api/rsc/routeRSCServerRequest.md
@@ -0,0 +1,49 @@
+---
+title: routeRSCServerRequest
+unstable: true
+---
+
+# routeRSCServerRequest
+
+[MODES: data]
+
+## Summary
+
+Routes the incoming request to the RSC server and appropriately proxies the server response for data / resource requests, or renders to HTML for a document request.
+
+```tsx filename=entry.ssr.tsx
+routeRSCServerRequest({
+ request,
+ fetchServer,
+ createFromReadableStream,
+ async renderHTML(getPayload) {
+ const payload = await getPayload();
+
+ return await renderHTMLToReadableStream(
+ ,
+ {
+ bootstrapScriptContent,
+ formState: await getFormState(payload),
+ }
+ );
+ },
+});
+```
+
+## Options
+
+### createFromReadableStream
+
+Your `react-server-dom-xyz/client`'s `createFromReadableStream` function, used to decode payloads from the server.
+
+### fetchServer
+
+A function that forwards a `Request` to the RSC handler and returns a `Promise` containing a serialized `RSCPayload`.
+
+### renderHTML
+
+A function that renders the `RSCPayload` to HTML, usually using a ``.
+
+### request
+
+The request to route.
diff --git a/docs/api/utils/createContext.md b/docs/api/utils/createContext.md
new file mode 100644
index 0000000000..ae4eaf56f2
--- /dev/null
+++ b/docs/api/utils/createContext.md
@@ -0,0 +1,80 @@
+---
+title: createContext
+unstable: true
+---
+
+# unstable_createContext
+
+[MODES: framework, data]
+
+
+
+
+This API is experimental and subject to breaking changes. Enable it with the `future.unstable_middleware` flag.
+
+## Summary
+
+[Reference Documentation โ](https://api.reactrouter.com/v7/functions/react_router.unstable_createContext.html)
+
+Creates a type-safe context object that can be used to store and retrieve values in middleware, loaders, and actions. Similar to React's `createContext`, but designed for React Router's request/response lifecycle.
+
+## Signature
+
+```tsx
+unstable_createContext(defaultValue?: T): RouterContext
+```
+
+## Params
+
+### defaultValue
+
+An optional default value for the context. This value will be returned if no value has been set for this context.
+
+## Returns
+
+A `RouterContext` object that can be used with `context.get()` and `context.set()` in middleware, loaders, and actions.
+
+## Examples
+
+### Basic Usage
+
+```tsx filename=app/context.ts
+import { unstable_createContext } from "react-router";
+
+// Create a context for user data
+export const userContext =
+ unstable_createContext(null);
+```
+
+```tsx filename=app/middleware/auth.ts
+import { userContext } from "~/context";
+import { getUserFromSession } from "~/auth.server";
+
+export const authMiddleware = async ({
+ request,
+ context,
+}) => {
+ const user = await getUserFromSession(request);
+ context.set(userContext, user);
+};
+```
+
+```tsx filename=app/routes/profile.tsx
+import { userContext } from "~/context";
+
+export async function loader({
+ context,
+}: Route.LoaderArgs) {
+ const user = context.get(userContext);
+
+ if (!user) {
+ throw new Response("Unauthorized", { status: 401 });
+ }
+
+ return { user };
+}
+```
+
+## See Also
+
+- [Middleware Guide](../../how-to/middleware)
diff --git a/docs/community/contributing.md b/docs/community/contributing.md
index 10d95c4fef..c5b8a7e90a 100644
--- a/docs/community/contributing.md
+++ b/docs/community/contributing.md
@@ -8,6 +8,10 @@ Thanks for contributing, you rock!
When it comes to open source, there are many different kinds of contributions that can be made, all of which are valuable. Here are a few guidelines that should help you as you prepare your contribution.
+## Open Governance Model
+
+Before going any further, please read the Open Governance [blog post](https://remix.run/blog/rr-governance) and [document](https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md) for information on how we handle bugs/issues/feature proposals in React Router.
+
## Setup
Before you can contribute to the codebase, you will need to fork the repo. This will look a bit different depending on what type of contribution you are making:
@@ -33,21 +37,23 @@ The following steps will get you set up to contribute changes to this repo:
## Think You Found a Bug?
-Please conform to the issue template and provide a clear path to reproduction with a code example. Best is a pull request with a [failing test](https://github.com/remix-run/react-router/blob/dev/integration/bug-report-test.ts). Next best is a link to [StackBlitz](https://reactrouter.com/new) or repository that illustrates the bug.
+Please conform to the issue template and provide a **minimal** and **runnable** reproduction. Best is a pull request with a [failing test](https://github.com/remix-run/react-router/blob/dev/integration/bug-report-test.ts). Next best is a link to [StackBlitz](https://reactrouter.com/new), CodeSsndbox, or GitHub repository that illustrates the bug.
-## Adding an Example?
+## Issue Not Getting Attention?
-Examples can be added directly to the main branch. Create a branch off of your local clone of main. Once you've finished, create a pull request and outline your example.
+If you need a bug fixed and nobody is fixing it, your best bet is to provide a fix for it and make a [pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request). Open source code belongs to all of us, and it's all of our responsibility to push it forward.
## Proposing New or Changed API?
-Please provide thoughtful comments and some sample code that show what you'd like to do with React Router in your app. It helps the conversation if you can show us how you're limited by the current API first before jumping to a conclusion about what needs to be changed and/or added.
+โ ๏ธ _Please do not start with a PR for a new feature._
+
+New features need to go through the process outlined in the [Open Governance Model](https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#new-feature-process) and can be started by opening a [Proposal Discussion](https://github.com/remix-run/react-router/discussions/new?category=proposals) on GitHub. Please provide thoughtful comments and some sample code that show what you'd like to do with React Router in your app. It helps the conversation if you can show us how you're limited by the current API first before jumping to a conclusion about what needs to be changed and/or added.
We have learned by experience that small APIs are usually better, so we may be a little reluctant to add something new unless there's an obvious limitation with the current API. That being said, we are always anxious to hear about cases that we just haven't considered before, so please don't be shy! :)
-## Issue Not Getting Attention?
+## Adding an Example?
-If you need a bug fixed and nobody is fixing it, your best bet is to provide a fix for it and make a [pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request). Open source code belongs to all of us, and it's all of our responsibility to push it forward.
+Examples can be added directly to the `main` branch. Create a branch off of your local clone of `main`. Once you've finished, create a pull request and outline your example.
## Making a Pull Request?
@@ -57,7 +63,7 @@ Pull requests need only the approval of two or more collaborators to be merged;
### Tests
-All commits that fix bugs or add features need a test.
+All commits that fix bugs or add features need one or more tests.
Do not merge code without tests!
@@ -83,14 +89,14 @@ Calling `pnpm build` from the root directory will run the build, which should ta
### Testing
-Before running the tests, you need to run a build. After you build, running `pnpm test` from the root directory will run **every** package's tests. If you want to run tests for a specific package, use `pnpm test --projects packages/`:
+Before running the tests, you need to run a build. After you build, running `pnpm test` from the root directory will run **every** package's tests. If you want to run tests for a specific package, use `pnpm test packages//`:
```bash
# Test all packages
pnpm test
-# Test only react-router-dom
-pnpm test --projects packages/react-router-dom
+# Test only @react-router/dev
+pnpm test packages/react-router-dev/
```
## Repository Branching
@@ -100,84 +106,11 @@ This repo maintains separate branches for different purposes. They will look som
```
- main > the most recent release and current docs
- dev > code under active development between stable releases
-- v5 > the most recent code for a specific major release
+- v6 > the most recent code for a specific major release
```
There may be other branches for various features and experimentation, but all of the magic happens from these branches.
-## New Releases
+## Releases
-When it's time to cut a new release, we follow a process based on our branching strategy depending on the type of release.
-
-### `react-router@next` Releases
-
-We create experimental releases from the current state of the `dev` branch. They can be installed by using the `@next` tag:
-
-```bash
-pnpm add react-router-dom@next
-# or
-npm install react-router-dom@next
-```
-
-These releases will be automated as PRs are merged into the `dev` branch.
-
-### Latest Major Releases
-
-```bash
-# Start from the dev branch.
-git checkout dev
-
-# Merge the main branch into dev to ensure that any hotfixes and
-# docs updates are available in the release.
-git merge main
-
-# Create a new release branch from dev.
-git checkout -b release/v6.1.0
-
-# Create a new tag and update version references throughout the
-# codebase.
-pnpm run version [nextVersion]
-
-# Push the release branch along with the new release tag.
-git push origin release/v6.1.0 --follow-tags
-
-# Wait for GitHub actions to run all tests. If the tests pass, the
-# release is ready to go! Merge the release branch into main and dev.
-git checkout main
-git merge release/v6.1.0
-git checkout dev
-git merge release/v6.1.0
-
-# The release branch can now be deleted.
-git branch -D release/v6.1.0
-git push origin --delete release/v6.1.0
-
-# Now go to GitHub and create the release from the new tag. Let
-# GitHub Actions take care of the rest!
-```
-
-### Hot-fix Releases
-
-Sometimes we have a crucial bug that needs to be patched right away. If the bug affects the latest release, we can create a new version directly from `main` (or the relevant major release branch where the bug exists):
-
-```bash
-# From the main branch, make sure to run the build and all tests
-# before creating a new release.
-pnpm install && pnpm build && pnpm test
-
-# Assuming the tests pass, create the release tag and update
-# version references throughout the codebase.
-pnpm run version [nextVersion]
-
-# Push changes along with the new release tag.
-git push origin main --follow-tags
-
-# In GitHub, create the release from the new tag and it will be
-# published via GitHub actions
-
-# When the hot-fix is done, merge the changes into dev and clean
-# up conflicts as needed.
-git checkout dev
-git merge main
-git push origin dev
-```
+Please refer to [DEVELOPMENT.md](https://github.com/remix-run/react-router/blob/main/DEVELOPMENT.md) for an outline of the release process.
diff --git a/docs/explanation/backend-for-frontend.md b/docs/explanation/backend-for-frontend.md
new file mode 100644
index 0000000000..252b66b51a
--- /dev/null
+++ b/docs/explanation/backend-for-frontend.md
@@ -0,0 +1,50 @@
+---
+title: Backend For Frontend
+---
+
+# Backend For Frontend
+
+[MODES: framework]
+
+
+
+
+While React Router can serve as your fullstack application, it also fits perfectly into the "Backend for Frontend" architecture.
+
+The BFF strategy employs a web server with a job scoped to serving the frontend web app and connecting it to the services it needs: your database, mailer, job queues, existing backend APIs (REST, GraphQL), etc. Instead of your UI integrating directly from the browser to these services, it connects to the BFF, and the BFF connects to your services.
+
+Mature apps already have a lot of backend application code in Ruby, Elixir, PHP, etc., and there's no reason to justify migrating it all to a server-side JavaScript runtime just to get the benefits of React Router. Instead, you can use your React Router app as a backend for your frontend.
+
+You can use `fetch` right from your loaders and actions to your backend.
+
+```tsx lines=[7,13,17]
+import escapeHtml from "escape-html";
+
+export async function loader() {
+ const apiUrl = "https://api.example.com/some-data.json";
+ const res = await fetch(apiUrl, {
+ headers: {
+ Authorization: `Bearer ${process.env.API_TOKEN}`,
+ },
+ });
+
+ const data = await res.json();
+
+ const prunedData = data.map((record) => {
+ return {
+ id: record.id,
+ title: record.title,
+ formattedBody: escapeHtml(record.content),
+ };
+ });
+ return { prunedData };
+}
+```
+
+There are several benefits of this approach vs. fetching directly from the browser. The highlighted lines above show how you can:
+
+1. Simplify third-party integrations and keep tokens and secrets out of client bundles
+2. Prune the data down to send less kB over the network, speeding up your app significantly
+3. Move a lot of code from browser bundles to the server, like `escapeHtml`, which speeds up your app. Additionally, moving code to the server usually makes your code easier to maintain since server-side code doesn't have to worry about UI states for async operations
+
+Again, React Router can be used as your only server by talking directly to the database and other services with server-side JavaScript APIs, but it also works perfectly as a backend for your frontend. Go ahead and keep your existing API server for application logic and let React Router connect the UI to it.
diff --git a/docs/explanation/code-splitting.md b/docs/explanation/code-splitting.md
index e764299b0b..b78b5600d3 100644
--- a/docs/explanation/code-splitting.md
+++ b/docs/explanation/code-splitting.md
@@ -4,6 +4,11 @@ title: Automatic Code Splitting
# Automatic Code Splitting
+[MODES: framework]
+
+
+
+
When using React Router's framework features, your application is automatically code split to improve the performance of initial load times when users visit your application.
## Code Splitting by Route
diff --git a/docs/explanation/concurrency.md b/docs/explanation/concurrency.md
index b939ecca9e..cfa8695d53 100644
--- a/docs/explanation/concurrency.md
+++ b/docs/explanation/concurrency.md
@@ -6,7 +6,8 @@ title: Network Concurrency Management
[MODES: framework, data]
-## Overview
+
+
When building web applications, managing network requests can be a daunting task. The challenges of ensuring up-to-date data and handling simultaneous requests often lead to complex logic in the application to deal with interruptions and race conditions. React Router simplifies this process by automating network management while mirroring and expanding upon the intuitive behavior of web browsers.
diff --git a/docs/explanation/form-vs-fetcher.md b/docs/explanation/form-vs-fetcher.md
new file mode 100644
index 0000000000..5a011dc476
--- /dev/null
+++ b/docs/explanation/form-vs-fetcher.md
@@ -0,0 +1,293 @@
+---
+title: Form vs. fetcher
+---
+
+# Form vs. fetcher
+
+[MODES: framework, data]
+
+## Overview
+
+Developing in React Router offers a rich set of tools that can sometimes overlap in functionality, creating a sense of ambiguity for newcomers. The key to effective development in React Router is understanding the nuances and appropriate use cases for each tool. This document seeks to provide clarity on when and why to use specific APIs.
+
+## APIs in Focus
+
+- [`