diff --git a/.changeset/config.json b/.changeset/config.json
index 8e1dec4247..19a0281431 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -7,10 +7,17 @@
"commit": false,
"fixed": [
[
+ "create-react-router",
"react-router",
"react-router-dom",
- "react-router-dom-v5-compat",
- "react-router-native"
+ "@react-router/architect",
+ "@react-router/cloudflare",
+ "@react-router/dev",
+ "@react-router/fs-routes",
+ "@react-router/express",
+ "@react-router/node",
+ "@react-router/remix-routes-option-adapter",
+ "@react-router/serve"
]
],
"linked": [],
@@ -18,5 +25,8 @@
"baseBranch": "dev",
"updateInternalDependencies": "patch",
"bumpVersionsWithWorkspaceProtocolOnly": true,
- "ignore": []
+ "ignore": ["integration", "integration-*", "@playground/*"],
+ "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
+ "onlyUpdatePeerDependentsWhenOutOfRange": true
+ }
}
diff --git a/.eslintignore b/.eslintignore
index fb8eac2f82..d20bd96fff 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -3,6 +3,8 @@ node_modules/
pnpm-lock.yaml
/docs/api
examples/**/dist/
+/playground/
+/playground-local/
packages/**/dist/
packages/react-router-dom/server.d.ts
packages/react-router-dom/server.js
diff --git a/.eslintrc b/.eslintrc
index 728a4ef663..61400497f8 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -9,6 +9,16 @@
"files": ["**/__tests__/**"],
"plugins": ["jest"],
"extends": ["plugin:jest/recommended"]
+ },
+ {
+ "files": ["integration/**/*.*"],
+ "rules": {
+ "react-hooks/rules-of-hooks": "off"
+ },
+ "env": {
+ "jest/globals": false
+ }
}
- ]
+ ],
+ "reportUnusedDisableDirectives": true
}
diff --git a/.github/workflows/deduplicate-lock-file.yml b/.github/workflows/deduplicate-lock-file.yml
index 81b6d08d01..929df876e0 100644
--- a/.github/workflows/deduplicate-lock-file.yml
+++ b/.github/workflows/deduplicate-lock-file.yml
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v4
- name: ๐ฆ Setup pnpm
- uses: pnpm/action-setup@v3.0.0
+ uses: pnpm/action-setup@v4
- name: โ Setup node
uses: actions/setup-node@v4
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
index 7e6b410c39..cb7b558534 100644
--- a/.github/workflows/format.yml
+++ b/.github/workflows/format.yml
@@ -22,13 +22,13 @@ jobs:
token: ${{ secrets.FORMAT_PAT }}
- name: ๐ฆ Setup pnpm
- uses: pnpm/action-setup@v3.0.0
+ uses: pnpm/action-setup@v4
- name: โ Setup node
uses: actions/setup-node@v4
with:
- cache: pnpm
node-version-file: ".nvmrc"
+ cache: pnpm
- name: ๐ฅ Install deps
run: pnpm install --frozen-lockfile
diff --git a/.github/workflows/integration-full.yml b/.github/workflows/integration-full.yml
new file mode 100644
index 0000000000..3f79331816
--- /dev/null
+++ b/.github/workflows/integration-full.yml
@@ -0,0 +1,56 @@
+name: Branch
+
+# main/dev/release-* branches will get the full run across
+# all OS/browsers for multiple node versions
+
+on:
+ push:
+ branches:
+ - main
+ - dev
+ - release-*
+ tags:
+ - "v0.0.0-nightly-*"
+ paths-ignore:
+ - ".changeset/**"
+ - "decisions/**"
+ - "docs/**"
+ - "examples/**"
+ - "jest/**"
+ - "scripts/**"
+ - "tutorial/**"
+ - "contributors.yml"
+ - "**/*.md"
+
+jobs:
+ build:
+ name: "โ๏ธ Build"
+ if: github.repository == 'remix-run/react-router'
+ uses: ./.github/workflows/shared-build.yml
+
+ integration-ubuntu:
+ name: "๐ Integration Test"
+ if: github.repository == 'remix-run/react-router'
+ uses: ./.github/workflows/shared-integration.yml
+ with:
+ os: "ubuntu-latest"
+ node_version: "[20, 22]"
+ browser: '["chromium", "firefox"]'
+
+ integration-windows:
+ name: "๐ Integration Test"
+ if: github.repository == 'remix-run/react-router'
+ uses: ./.github/workflows/shared-integration.yml
+ with:
+ os: "windows-latest"
+ node_version: "[20, 22]"
+ browser: '["msedge"]'
+
+ integration-macos:
+ name: "๐ Integration Test"
+ if: github.repository == 'remix-run/react-router'
+ uses: ./.github/workflows/shared-integration.yml
+ with:
+ os: "macos-latest"
+ node_version: "[20, 22]"
+ browser: '["webkit"]'
diff --git a/.github/workflows/integration-pr-ubuntu.yml b/.github/workflows/integration-pr-ubuntu.yml
new file mode 100644
index 0000000000..ac25713e08
--- /dev/null
+++ b/.github/workflows/integration-pr-ubuntu.yml
@@ -0,0 +1,35 @@
+name: PR (Base)
+
+# All PRs touching code will run tests on ubuntu/node/chromium
+
+on:
+ pull_request:
+ paths-ignore:
+ - ".changeset/**"
+ - "decisions/**"
+ - "docs/**"
+ - "examples/**"
+ - "jest/**"
+ - "scripts/**"
+ - "tutorial/**"
+ - "contributors.yml"
+ - "**/*.md"
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: "โ๏ธ Build"
+ if: github.repository == 'remix-run/react-router'
+ uses: ./.github/workflows/shared-build.yml
+
+ integration-chromium:
+ name: "๐ Integration Test"
+ if: github.repository == 'remix-run/react-router'
+ uses: ./.github/workflows/shared-integration.yml
+ with:
+ os: "ubuntu-latest"
+ node_version: "[22]"
+ browser: '["chromium"]'
diff --git a/.github/workflows/integration-pr-windows-macos.yml b/.github/workflows/integration-pr-windows-macos.yml
new file mode 100644
index 0000000000..780a81f289
--- /dev/null
+++ b/.github/workflows/integration-pr-windows-macos.yml
@@ -0,0 +1,43 @@
+name: PR (Full)
+
+# PRs touching react-router-dev will also run on Windows and OSX
+
+on:
+ pull_request:
+ paths:
+ - "pnpm-lock.yaml"
+ - "integration/**"
+ - "packages/react-router-dev/**"
+ - "!**/*.md"
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ integration-firefox:
+ name: "๐ Integration Test"
+ if: github.repository == 'remix-run/react-router'
+ uses: ./.github/workflows/shared-integration.yml
+ with:
+ os: "ubuntu-latest"
+ node_version: "[22]"
+ browser: '["firefox"]'
+
+ integration-msedge:
+ name: "๐ Integration Test"
+ if: github.repository == 'remix-run/react-router'
+ uses: ./.github/workflows/shared-integration.yml
+ with:
+ os: "windows-latest"
+ node_version: "[22]"
+ browser: '["msedge"]'
+
+ integration-webkit:
+ name: "๐ Integration Test"
+ if: github.repository == 'remix-run/react-router'
+ uses: ./.github/workflows/shared-integration.yml
+ with:
+ os: "macos-latest"
+ node_version: "[22]"
+ browser: '["webkit"]'
diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml
index 45a3848fa2..c4da9dac7e 100644
--- a/.github/workflows/no-response.yml
+++ b/.github/workflows/no-response.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: ๐ฅบ Handle Ghosting
- uses: actions/stale@v8
+ uses: actions/stale@v9
with:
days-before-close: 10
close-issue-message: >
diff --git a/.github/workflows/release-experimental.yml b/.github/workflows/release-experimental.yml
index f69d4dd500..0a9437dc89 100644
--- a/.github/workflows/release-experimental.yml
+++ b/.github/workflows/release-experimental.yml
@@ -26,7 +26,7 @@ jobs:
fetch-depth: 0
- name: ๐ฆ Setup pnpm
- uses: pnpm/action-setup@v3.0.0
+ uses: pnpm/action-setup@v4
- name: โ Setup node
uses: actions/setup-node@v4
diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml
index 72c0aae27a..62d5db855b 100644
--- a/.github/workflows/release-nightly.yml
+++ b/.github/workflows/release-nightly.yml
@@ -37,7 +37,7 @@ jobs:
fetch-depth: 0
- name: ๐ฆ Setup pnpm
- uses: pnpm/action-setup@v3.0.0
+ uses: pnpm/action-setup@v4
- name: โ Setup node
uses: actions/setup-node@v4
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0d2329e622..7301ec56f9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -28,7 +28,7 @@ jobs:
fetch-depth: 0
- name: ๐ฆ Setup pnpm
- uses: pnpm/action-setup@v3.0.0
+ uses: pnpm/action-setup@v4
- name: โ Setup node
uses: actions/setup-node@v4
@@ -74,12 +74,12 @@ jobs:
uses: actions/checkout@v4
- name: ๐ฆ Setup pnpm
- uses: pnpm/action-setup@v3.0.0
+ uses: pnpm/action-setup@v4
- name: โ Setup node
uses: actions/setup-node@v4
with:
- node-version: 16
+ node-version-file: ".nvmrc"
cache: "pnpm"
- id: find_package_version
diff --git a/.github/workflows/shared-build.yml b/.github/workflows/shared-build.yml
new file mode 100644
index 0000000000..50bec16d99
--- /dev/null
+++ b/.github/workflows/shared-build.yml
@@ -0,0 +1,37 @@
+name: ๐ ๏ธ Build
+
+on:
+ workflow_call:
+
+env:
+ CI: true
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: โฌ๏ธ Checkout repo
+ uses: actions/checkout@v4
+
+ - name: ๐ฆ Setup pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: โ Setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: ".nvmrc"
+ cache: "pnpm"
+
+ - uses: google/wireit@setup-github-actions-caching/v2
+
+ - name: Disable GitHub Actions Annotations
+ run: |
+ echo "::remove-matcher owner=tsc::"
+ echo "::remove-matcher owner=eslint-compact::"
+ echo "::remove-matcher owner=eslint-stylish::"
+
+ - name: ๐ฅ Install deps
+ run: pnpm install --frozen-lockfile
+
+ - name: ๐ Build
+ run: pnpm build
diff --git a/.github/workflows/shared-integration.yml b/.github/workflows/shared-integration.yml
new file mode 100644
index 0000000000..ee0d86a340
--- /dev/null
+++ b/.github/workflows/shared-integration.yml
@@ -0,0 +1,64 @@
+name: ๐งช Test (Integration)
+
+on:
+ workflow_call:
+ inputs:
+ os:
+ required: true
+ type: string
+ node_version:
+ required: true
+ # this is limited to string | boolean | number (https://github.community/t/can-action-inputs-be-arrays/16457)
+ # but we want to pass an array (node_version: "[20, 22]"),
+ # so we'll need to manually stringify it for now
+ type: string
+ browser:
+ required: true
+ # this is limited to string | boolean | number (https://github.community/t/can-action-inputs-be-arrays/16457)
+ # but we want to pass an array (browser: "['chromium', 'firefox']"),
+ # so we'll need to manually stringify it for now
+ type: string
+
+env:
+ CI: true
+
+jobs:
+ integration:
+ name: "${{ inputs.os }} / node@${{ matrix.node }} / ${{ matrix.browser }}"
+ strategy:
+ fail-fast: false
+ matrix:
+ node: ${{ fromJSON(inputs.node_version) }}
+ browser: ${{ fromJSON(inputs.browser) }}
+
+ runs-on: ${{ inputs.os }}
+ steps:
+ - name: โฌ๏ธ Checkout repo
+ uses: actions/checkout@v4
+
+ - name: ๐ฆ Setup pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: โ Setup node ${{ matrix.node }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node }}
+ cache: "pnpm"
+
+ - uses: google/wireit@setup-github-actions-caching/v2
+
+ - name: Disable GitHub Actions Annotations
+ run: |
+ echo "::remove-matcher owner=tsc::"
+ echo "::remove-matcher owner=eslint-compact::"
+ echo "::remove-matcher owner=eslint-stylish::"
+
+ - name: ๐ฅ Install deps
+ run: pnpm install --frozen-lockfile
+
+ - name: ๐ฅ Install Playwright
+ run: npx playwright install --with-deps ${{ matrix.browser }}
+
+ - name: ๐ Run Integration Tests ${{ matrix.browser }}
+ run: "pnpm test:integration --project=${{ matrix.browser }}"
+ timeout-minutes: 40
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 3fbb8d9cfa..7a364dd55c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -26,8 +26,8 @@ jobs:
fail-fast: false
matrix:
node:
- - 16
- - 18
+ - 20
+ - 22
runs-on: ubuntu-latest
@@ -36,14 +36,16 @@ jobs:
uses: actions/checkout@v4
- name: ๐ฆ Setup pnpm
- uses: pnpm/action-setup@v3.0.0
+ uses: pnpm/action-setup@v4
- name: โ Setup node
uses: actions/setup-node@v4
with:
+ node-version: ${{ matrix.node }}
cache: pnpm
check-latest: true
- node-version: ${{ matrix.node }}
+
+ - uses: google/wireit@setup-github-actions-caching/v2
- name: Disable GitHub Actions Annotations
run: |
@@ -57,11 +59,11 @@ jobs:
- name: ๐ Build
run: pnpm build
+ - name: ๐ Typecheck
+ run: pnpm typecheck
+
- name: ๐ฌ Lint
run: pnpm lint
- name: ๐งช Run tests
run: pnpm test
-
- - name: Check bundle size
- run: pnpm size
diff --git a/.gitignore b/.gitignore
index 373afd7082..70fce75324 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,8 @@ node_modules/
/examples/*/pnpm-lock.yaml
/examples/*/dist
/tutorial/dist
+/playground-local/
+/integration/playwright-report
# v5 build files
/packages/*/cjs/
@@ -20,9 +22,14 @@ node_modules/
/packages/*/dist/
/packages/*/LICENSE.md
-# compat module copies
-/packages/react-router-dom-v5-compat/react-router-dom
+# v7 build files
+.react-router
+.wireit
.eslintcache
+.tmp
/.env
/NOTES.md
+
+# v7 reference docs
+/public
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6e2a02e689..1259307b45 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,193 +13,218 @@ We manage release notes in this file instead of the paginated Github Releases Pa
Table of Contents
- [React Router Releases](#react-router-releases)
- - [v6.28.0](#v6280)
- - [What's Changed](#whats-changed)
+ - [v7.0.0](#v700)
+ - [Breaking Changes](#breaking-changes)
+ - [Package Restructuring](#package-restructuring)
+ - [Removed Adapter Re-exports](#removed-adapter-re-exports)
+ - [Removed APIs](#removed-apis)
+ - [Minimum Versions](#minimum-versions)
+ - [Adopted Future Flag Behaviors](#adopted-future-flag-behaviors)
+ - [Vite Compiler](#vite-compiler)
+ - [Exposed Router Promises](#exposed-router-promises)
+ - [Other Notable Changes](#other-notable-changes)
+ - [`routes.ts`](#routests)
+ - [Typesafety improvements](#typesafety-improvements)
+ - [Setup](#setup)
+ - [`typegen` command](#typegen-command)
+ - [TypeScript plugin](#typescript-plugin)
+ - [VSCode](#vscode)
+ - [Troubleshooting](#troubleshooting)
+ - [Prerendering](#prerendering)
+ - [Major Changes (`react-router`)](#major-changes-react-router)
+ - [Major Changes (`@react-router/*`)](#major-changes-react-router-1)
- [Minor Changes](#minor-changes)
- [Patch Changes](#patch-changes)
+ - [Changes by Package](#changes-by-package)
+- [**Full Changelog**: `v6.28.0...v7.0.0`](#full-changelog-v6280v700)
+ - [v6.28.0](#v6280)
+ - [What's Changed](#whats-changed)
+ - [Minor Changes](#minor-changes-1)
+ - [Patch Changes](#patch-changes-1)
+- [\<\<\<\<\<\<\< HEAD](#-head)
- [v6.27.0](#v6270)
- [What's Changed](#whats-changed-1)
- [Stabilized APIs](#stabilized-apis)
- - [Minor Changes](#minor-changes-1)
- - [Patch Changes](#patch-changes-1)
- - [v6.26.2](#v6262)
+ - [Minor Changes](#minor-changes-2)
- [Patch Changes](#patch-changes-2)
- - [v6.26.1](#v6261)
+ - [v6.26.2](#v6262)
- [Patch Changes](#patch-changes-3)
- - [v6.26.0](#v6260)
- - [Minor Changes](#minor-changes-2)
+ - [v6.26.1](#v6261)
- [Patch Changes](#patch-changes-4)
- - [v6.25.1](#v6251)
+ - [v6.26.0](#v6260)
+ - [Minor Changes](#minor-changes-3)
- [Patch Changes](#patch-changes-5)
+ - [v6.25.1](#v6251)
+ - [Patch Changes](#patch-changes-6)
- [v6.25.0](#v6250)
- [What's Changed](#whats-changed-2)
- [Stabilized `v7_skipActionErrorRevalidation`](#stabilized-v7_skipactionerrorrevalidation)
- - [Minor Changes](#minor-changes-3)
- - [Patch Changes](#patch-changes-6)
- - [v6.24.1](#v6241)
+ - [Minor Changes](#minor-changes-4)
- [Patch Changes](#patch-changes-7)
+ - [v6.24.1](#v6241)
+ - [Patch Changes](#patch-changes-8)
- [v6.24.0](#v6240)
- [What's Changed](#whats-changed-3)
- [Lazy Route Discovery (a.k.a. "Fog of War")](#lazy-route-discovery-aka-fog-of-war)
- - [Minor Changes](#minor-changes-4)
- - [Patch Changes](#patch-changes-8)
- - [v6.23.1](#v6231)
+ - [Minor Changes](#minor-changes-5)
- [Patch Changes](#patch-changes-9)
+ - [v6.23.1](#v6231)
+ - [Patch Changes](#patch-changes-10)
- [v6.23.0](#v6230)
- [What's Changed](#whats-changed-4)
- [Data Strategy (unstable)](#data-strategy-unstable)
- [Skip Action Error Revalidation (unstable)](#skip-action-error-revalidation-unstable)
- - [Minor Changes](#minor-changes-5)
+ - [Minor Changes](#minor-changes-6)
- [v6.22.3](#v6223)
- - [Patch Changes](#patch-changes-10)
- - [v6.22.2](#v6222)
- [Patch Changes](#patch-changes-11)
- - [v6.22.1](#v6221)
+ - [v6.22.2](#v6222)
- [Patch Changes](#patch-changes-12)
+ - [v6.22.1](#v6221)
+ - [Patch Changes](#patch-changes-13)
- [v6.22.0](#v6220)
- [What's Changed](#whats-changed-5)
- [Core Web Vitals Technology Report Flag](#core-web-vitals-technology-report-flag)
- - [Minor Changes](#minor-changes-6)
- - [Patch Changes](#patch-changes-13)
- - [v6.21.3](#v6213)
+ - [Minor Changes](#minor-changes-7)
- [Patch Changes](#patch-changes-14)
- - [v6.21.2](#v6212)
+ - [v6.21.3](#v6213)
- [Patch Changes](#patch-changes-15)
- - [v6.21.1](#v6211)
+ - [v6.21.2](#v6212)
- [Patch Changes](#patch-changes-16)
+ - [v6.21.1](#v6211)
+ - [Patch Changes](#patch-changes-17)
- [v6.21.0](#v6210)
- [What's Changed](#whats-changed-6)
- [`future.v7_relativeSplatPath`](#futurev7_relativesplatpath)
- [Partial Hydration](#partial-hydration)
- - [Minor Changes](#minor-changes-7)
- - [Patch Changes](#patch-changes-17)
- - [v6.20.1](#v6201)
- - [Patch Changes](#patch-changes-18)
- - [v6.20.0](#v6200)
- [Minor Changes](#minor-changes-8)
+ - [Patch Changes](#patch-changes-18)
+ - [v6.20.1](#v6201)
- [Patch Changes](#patch-changes-19)
+ - [v6.20.0](#v6200)
+ - [Minor Changes](#minor-changes-9)
+ - [Patch Changes](#patch-changes-20)
- [v6.19.0](#v6190)
- [What's Changed](#whats-changed-7)
- [`unstable_flushSync` API](#unstable_flushsync-api)
- - [Minor Changes](#minor-changes-9)
- - [Patch Changes](#patch-changes-20)
+ - [Minor Changes](#minor-changes-10)
+ - [Patch Changes](#patch-changes-21)
- [v6.18.0](#v6180)
- [What's Changed](#whats-changed-8)
- [New Fetcher APIs](#new-fetcher-apis)
- [Persistence Future Flag (`future.v7_fetcherPersist`)](#persistence-future-flag-futurev7_fetcherpersist)
- - [Minor Changes](#minor-changes-10)
- - [Patch Changes](#patch-changes-21)
+ - [Minor Changes](#minor-changes-11)
+ - [Patch Changes](#patch-changes-22)
- [v6.17.0](#v6170)
- [What's Changed](#whats-changed-9)
- [View Transitions ๐](#view-transitions-)
- - [Minor Changes](#minor-changes-11)
- - [Patch Changes](#patch-changes-22)
- - [v6.16.0](#v6160)
- [Minor Changes](#minor-changes-12)
- [Patch Changes](#patch-changes-23)
- - [v6.15.0](#v6150)
+ - [v6.16.0](#v6160)
- [Minor Changes](#minor-changes-13)
- [Patch Changes](#patch-changes-24)
- - [v6.14.2](#v6142)
+ - [v6.15.0](#v6150)
+ - [Minor Changes](#minor-changes-14)
- [Patch Changes](#patch-changes-25)
- - [v6.14.1](#v6141)
+ - [v6.14.2](#v6142)
- [Patch Changes](#patch-changes-26)
+ - [v6.14.1](#v6141)
+ - [Patch Changes](#patch-changes-27)
- [v6.14.0](#v6140)
- [What's Changed](#whats-changed-10)
- [JSON/Text Submissions](#jsontext-submissions)
- - [Minor Changes](#minor-changes-14)
- - [Patch Changes](#patch-changes-27)
+ - [Minor Changes](#minor-changes-15)
+ - [Patch Changes](#patch-changes-28)
- [v6.13.0](#v6130)
- [What's Changed](#whats-changed-11)
- [`future.v7_startTransition`](#futurev7_starttransition)
- - [Minor Changes](#minor-changes-15)
- - [Patch Changes](#patch-changes-28)
- - [v6.12.1](#v6121)
+ - [Minor Changes](#minor-changes-16)
- [Patch Changes](#patch-changes-29)
+ - [v6.12.1](#v6121)
+ - [Patch Changes](#patch-changes-30)
- [v6.12.0](#v6120)
- [What's Changed](#whats-changed-12)
- [`React.startTransition` support](#reactstarttransition-support)
- - [Minor Changes](#minor-changes-16)
- - [Patch Changes](#patch-changes-30)
- - [v6.11.2](#v6112)
+ - [Minor Changes](#minor-changes-17)
- [Patch Changes](#patch-changes-31)
- - [v6.11.1](#v6111)
+ - [v6.11.2](#v6112)
- [Patch Changes](#patch-changes-32)
- - [v6.11.0](#v6110)
- - [Minor Changes](#minor-changes-17)
+ - [v6.11.1](#v6111)
- [Patch Changes](#patch-changes-33)
+ - [v6.11.0](#v6110)
+ - [Minor Changes](#minor-changes-18)
+ - [Patch Changes](#patch-changes-34)
- [v6.10.0](#v6100)
- [What's Changed](#whats-changed-13)
- - [Minor Changes](#minor-changes-18)
+ - [Minor Changes](#minor-changes-19)
- [`future.v7_normalizeFormMethod`](#futurev7_normalizeformmethod)
- - [Patch Changes](#patch-changes-34)
+ - [Patch Changes](#patch-changes-35)
- [v6.9.0](#v690)
- [What's Changed](#whats-changed-14)
- [`Component`/`ErrorBoundary` route properties](#componenterrorboundary-route-properties)
- [Introducing Lazy Route Modules](#introducing-lazy-route-modules)
- - [Minor Changes](#minor-changes-19)
- - [Patch Changes](#patch-changes-35)
- - [v6.8.2](#v682)
+ - [Minor Changes](#minor-changes-20)
- [Patch Changes](#patch-changes-36)
- - [v6.8.1](#v681)
+ - [v6.8.2](#v682)
- [Patch Changes](#patch-changes-37)
- - [v6.8.0](#v680)
- - [Minor Changes](#minor-changes-20)
+ - [v6.8.1](#v681)
- [Patch Changes](#patch-changes-38)
- - [v6.7.0](#v670)
+ - [v6.8.0](#v680)
- [Minor Changes](#minor-changes-21)
- [Patch Changes](#patch-changes-39)
- - [v6.6.2](#v662)
+ - [v6.7.0](#v670)
+ - [Minor Changes](#minor-changes-22)
- [Patch Changes](#patch-changes-40)
- - [v6.6.1](#v661)
+ - [v6.6.2](#v662)
- [Patch Changes](#patch-changes-41)
+ - [v6.6.1](#v661)
+ - [Patch Changes](#patch-changes-42)
- [v6.6.0](#v660)
- [What's Changed](#whats-changed-15)
- - [Minor Changes](#minor-changes-22)
- - [Patch Changes](#patch-changes-42)
- - [v6.5.0](#v650)
- - [What's Changed](#whats-changed-16)
- [Minor Changes](#minor-changes-23)
- [Patch Changes](#patch-changes-43)
- - [v6.4.5](#v645)
+ - [v6.5.0](#v650)
+ - [What's Changed](#whats-changed-16)
+ - [Minor Changes](#minor-changes-24)
- [Patch Changes](#patch-changes-44)
- - [v6.4.4](#v644)
+ - [v6.4.5](#v645)
- [Patch Changes](#patch-changes-45)
- - [v6.4.3](#v643)
+ - [v6.4.4](#v644)
- [Patch Changes](#patch-changes-46)
- - [v6.4.2](#v642)
+ - [v6.4.3](#v643)
- [Patch Changes](#patch-changes-47)
- - [v6.4.1](#v641)
+ - [v6.4.2](#v642)
- [Patch Changes](#patch-changes-48)
+ - [v6.4.1](#v641)
+ - [Patch Changes](#patch-changes-49)
- [v6.4.0](#v640)
- [What's Changed](#whats-changed-17)
- [Remix Data APIs](#remix-data-apis)
- - [Patch Changes](#patch-changes-49)
+ - [Patch Changes](#patch-changes-50)
- [v6.3.0](#v630)
- - [Minor Changes](#minor-changes-24)
+ - [Minor Changes](#minor-changes-25)
- [v6.2.2](#v622)
- - [Patch Changes](#patch-changes-50)
- - [v6.2.1](#v621)
- [Patch Changes](#patch-changes-51)
- - [v6.2.0](#v620)
- - [Minor Changes](#minor-changes-25)
+ - [v6.2.1](#v621)
- [Patch Changes](#patch-changes-52)
- - [v6.1.1](#v611)
- - [Patch Changes](#patch-changes-53)
- - [v6.1.0](#v610)
+ - [v6.2.0](#v620)
- [Minor Changes](#minor-changes-26)
+ - [Patch Changes](#patch-changes-53)
+ - [v6.1.1](#v611)
- [Patch Changes](#patch-changes-54)
- - [v6.0.2](#v602)
+ - [v6.1.0](#v610)
+ - [Minor Changes](#minor-changes-27)
- [Patch Changes](#patch-changes-55)
- - [v6.0.1](#v601)
+ - [v6.0.2](#v602)
- [Patch Changes](#patch-changes-56)
+ - [v6.0.1](#v601)
+ - [Patch Changes](#patch-changes-57)
- [v6.0.0](#v600)
+## v7.0.0
+
+Date: 2024-11-21
+
+### Breaking Changes
+
+#### Package Restructuring
+
+- The `react-router-dom`, `@remix-run/react`, `@remix-run/server-runtime`, and `@remix-run/router` have been collapsed into the `react-router` package
+ - To ease migration, `react-router-dom` is still published in v7 as a re-export of everything from `react-router`
+- The `@remix-run/cloudflare-pages` and `@remix-run/cloudflare-workers` have been collapsed into `@react-router/cloudflare` package`
+- The `react-router-dom-v5-compat` and `react-router-native` packages are removed starting with v7
+
+#### Removed Adapter Re-exports
+
+Remix v2 used to re-export all common `@remix-run/server-runtime` APIs through the various runtime packages (`node`, `cloudflare`, `deno`) so that you wouldn't need an additional `@remix-run/server-runtime` dependency in your `package.json`. With the collapsing of packages into `react-router`, these common APIs are now no longer re-exported through the runtime adapters. You should import all common APIs from `react-router`, and only import runtime-specific APIs from the runtime packages:
+
+```jsx
+// Runtime-specific APIs
+import { createFileSessionStorage } from "@react-router/node";
+// Runtime-agnostic APIs
+import { redirect, useLoaderData } from "react-router";
+```
+
+#### Removed APIs
+
+The following APIs have been removed in React Router v7:
+
+- `json`
+- `defer`
+- `unstable_composeUploadHandlers`
+- `unstable_createMemoryUploadHandler`
+- `unstable_parseMultipartFormData`
+
+#### Minimum Versions
+
+React Router v7 requires the following minimum versions:
+
+- `node@20`
+ - React Router no longer provides an `installGlobals` method to [polyfill](https://reactrouter.com/dev/guides/deploying/custom-node#polyfilling-fetch) the `fetch` API
+- `react@18`, `react-dom@18`
+
+#### Adopted Future Flag Behaviors
+
+Remix and React Router follow an [API Development Strategy](https://reactrouter.com/en/main/guides/api-development-strategy) leveraging "Future Flags" to avoid introducing a slew of breaking changes in a major release. Instead, breaking changes are introduce din minor releases behind a flag, allowing users to opt-in at their convenience. In the next major release, all future flag behaviors become the default behavior.
+
+The following previously flagged behaviors are now the default in React Router v7:
+
+- [React Router v6 flags](https://reactrouter.com/en/v6/upgrading/future)
+ - `future.v7_relativeSplatPath`
+ - `future.v7_startTransition`
+ - `future.v7_fetcherPersist`
+ - `future.v7_normalizeFormMethod`
+ - `future.v7_partialHydration`
+ - `future.v7_skipActionStatusRevalidation`
+- [Remix v2 flags](https://remix.run/docs/en/v2/start/future-flags)
+ - `future.v3_fetcherPersist`
+ - `future.v3_relativeSplatPath`
+ - `future.v3_throwAbortReason`
+ - `future.v3_singleFetch`
+ - `future.v3_lazyRouteDiscovery`
+ - `future.v3_optimizeDeps`
+
+#### Vite Compiler
+
+The [Remix Vite plugin](https://remix.run/docs/en/2.12.1/start/future-flags#vite-plugin) is the proper way to build full-stack SSR apps using React Router v7. The former `esbuild`-based compiler is no longer available.
+
+**Renamed `vitePlugin` and `cloudflareDevProxyVitePlugin`**
+
+For Remix consumers migrating to React Router, the `vitePlugin` and `cloudflareDevProxyVitePlugin` exports have been renamed and moved ([#11904](https://github.com/remix-run/react-router/pull/11904))
+
+```diff
+-import {
+- vitePlugin as remix,
+- cloudflareDevProxyVitePlugin,
+-} from "@remix/dev";
+
++import { reactRouter } from "@react-router/dev/vite";
++import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
+```
+
+**Removed Vite Plugin `manifest` option**
+
+For Remix consumers migrating to React Router, the Vite plugin's `manifest` option has been removed. The `manifest` option been superseded by the more powerful `buildEnd` hook since it's passed the `buildManifest` argument. You can still write the build manifest to disk if needed, but you'll most likely find it more convenient to write any logic depending on the build manifest within the `buildEnd` hook itself. ([#11573](https://github.com/remix-run/react-router/pull/11573))
+
+If you were using the `manifest` option, you can replace it with a `buildEnd` hook that writes the manifest to disk like this:
+
+```js
+import { reactRouter } from "@react-router/dev/vite";
+import { writeFile } from "node:fs/promises";
+
+export default {
+ plugins: [
+ reactRouter({
+ async buildEnd({ buildManifest }) {
+ await writeFile(
+ "build/manifest.json",
+ JSON.stringify(buildManifest, null, 2),
+ "utf-8"
+ );
+ },
+ }),
+ ],
+};
+```
+
+#### Exposed Router Promises
+
+Because React 19 will have first-class support for handling promises in the render pass (via `React.use` and `useAction`), we are now comfortable exposing the promises for the APIs that previously returned `undefined`:
+
+- `useNavigate()`
+- `useSubmit()`
+- `useFetcher().load`
+- `useFetcher().submit`
+- `useRevalidator().revalidate()`
+
+### Other Notable Changes
+
+#### `routes.ts`
+
+When using the React Router Vite plugin, routes are defined in `app/routes.ts`. Route config is exported via the `routes` export, conforming to the `RouteConfig` type. Route helper functions `route`, `index`, and `layout` are provided to make declarative type-safe route definitions easier.
+
+```ts
+// app/routes.ts
+import {
+ type RouteConfig,
+ route,
+ index,
+ layout,
+} from "@react-router/dev/routes";
+
+export const routes: RouteConfig = [
+ index("./home.tsx"),
+ route("about", "./about.tsx"),
+
+ layout("./auth/layout.tsx", [
+ route("login", "./auth/login.tsx"),
+ route("register", "./auth/register.tsx"),
+ ]),
+
+ route("concerts", [
+ index("./concerts/home.tsx"),
+ route(":city", "./concerts/city.tsx"),
+ route("trending", "./concerts/trending.tsx"),
+ ]),
+];
+```
+
+For Remix consumers migrating to React Router, you can still configure file system routing within `routes.ts` using the `@react-router/fs-routes` package. A minimal route config that reproduces the default Remix setup looks like this:
+
+```ts
+// app/routes.ts
+import { type RouteConfig } from "@react-router/dev/routes";
+import { flatRoutes } from "@react-router/fs-routes";
+
+export const routes: RouteConfig = flatRoutes();
+```
+
+If you want to migrate from file system routing to config-based routes, you can mix and match approaches by spreading the results of the async `flatRoutes` function into the array of config-based routes.
+
+```ts
+// app/routes.ts
+import { type RouteConfig, route } from "@react-router/dev/routes";
+import { flatRoutes } from "@react-router/fs-routes";
+
+export const routes: RouteConfig = [
+ // Example config-based route:
+ route("/hello", "./routes/hello.tsx"),
+
+ // File system routes scoped to a different directory:
+ ...(await flatRoutes({
+ rootDirectory: "fs-routes",
+ })),
+];
+```
+
+If you were using Remix's `routes` option to use alternative file system routing conventions, you can adapt these to the new `RouteConfig` format using `@react-router/remix-config-routes-adapter`.
+
+For example, if you were using [Remix v1 route conventions](https://remix.run/docs/en/1.19.3/file-conventions/routes-files) in Remix v2, you can combine `@react-router/remix-config-routes-adapter` with `@remix-run/v1-route-convention` to adapt this to React Router:
+
+```ts
+// app/routes.ts
+import { type RouteConfig } from "@react-router/dev/routes";
+import { remixConfigRoutes } from "@react-router/remix-config-routes-adapter";
+import { createRoutesFromFolders } from "@remix-run/v1-route-convention";
+
+export const routes: RouteConfig = remixConfigRoutes(async (defineRoutes) => {
+ return createRoutesFromFolders(defineRoutes, {
+ ignoredFilePatterns: ["**/.*", "**/*.css"],
+ });
+});
+```
+
+Also note that, if you were using Remix's `routes` option to define config-based routes, you can also adapt these to the new `RouteConfig` format using `@react-router/remix-config-routes-adapter` with minimal code changes. While this makes for a fast migration path, we recommend migrating any config-based routes from Remix to the new `RouteConfig` format since it's a fairly straightforward migration.
+
+```diff
+// app/routes.ts
+-import { type RouteConfig } from "@react-router/dev/routes";
++import { type RouteConfig, route } from "@react-router/dev/routes";
+-import { remixConfigRoutes } from "@react-router/remix-config-routes-adapter";
+
+-export const routes: RouteConfig = remixConfigRoutes(async (defineRoutes) => {
+- defineRoutes((route) => {
+- route("/parent", "./routes/parent.tsx", () => [
+- route("/child", "./routes/child.tsx"),
+- ]);
+- });
+-});
++export const routes: RouteConfig = [
++ route("/parent", "./routes/parent.tsx", [
++ route("/child", "./routes/child.tsx"),
++ ]),
++];
+```
+
+#### Typesafety improvements
+
+React Router now generates types for each of your route modules and passes typed props to route module component exports ([#11961](https://github.com/remix-run/react-router/pull/11961), [#12019](https://github.com/remix-run/react-router/pull/12019)). You can access those types by importing them from `./+types.`.
+
+For example:
+
+```ts
+// app/routes/product.tsx
+import type * as Route from "./+types.product";
+
+export function loader({ params }: Route.LoaderArgs) {}
+
+export default function Component({ loaderData }: Route.ComponentProps) {}
+```
+
+This initial implementation targets type inference for:
+
+- `Params` : Path parameters from your routing config in `routes.ts` including file-based routing
+- `LoaderData` : Loader data from `loader` and/or `clientLoader` within your route module
+- `ActionData` : Action data from `action` and/or `clientAction` within your route module
+
+These types are then used to create types for route export args and props:
+
+- `LoaderArgs`
+- `ClientLoaderArgs`
+- `ActionArgs`
+- `ClientActionArgs`
+- `HydrateFallbackProps`
+- `ComponentProps` (for the `default` export)
+- `ErrorBoundaryProps`
+
+In the future, we plan to add types for the rest of the route module exports: `meta`, `links`, `headers`, `shouldRevalidate`, etc.
+
+We also plan to generate types for typesafe `Link`s:
+
+```tsx
+
+// ^^^^^^^^^^^^^ ^^^^^^^^^
+// typesafe `to` and `params` based on the available routes in your app
+```
+
+##### Setup
+
+React Router will generate types into a `.react-router/` directory at the root of your app. This directory is fully managed by React Router and is derived based on your route config (`routes.ts`).
+
+๐ **Add `.react-router/` to `.gitignore`**
+
+```txt
+.react-router
+```
+
+You should also ensure that generated types for routes are always present before running typechecking, especially for running typechecking in CI.
+
+๐ **Add `react-router typegen` to your `typecheck` command in `package.json`**
+
+```json
+{
+ "scripts": {
+ "typecheck": "react-router typegen && tsc"
+ }
+}
+```
+
+To get TypeScript to use those generated types, you'll need to add them to `include` in `tsconfig.json`. And to be able to import them as if they files next to your route modules, you'll also need to configure `rootDirs`.
+
+๐ **Configure `tsconfig.json` for generated types**
+
+```json
+{
+ "include": [".react-router/types/**/*"],
+ "compilerOptions": {
+ "rootDirs": [".", "./.react-router/types"]
+ }
+}
+```
+
+##### `typegen` command
+
+You can manually generate types with the new `typegen` command:
+
+```sh
+react-router typegen
+```
+
+However, manual type generation is tedious and types can get out of sync quickly if you ever forget to run `typegen`. Instead, we recommend that you setup our new TypeScript plugin which will automatically generate fresh types whenever routes change. That way, you'll always have up-to-date types.
+
+##### TypeScript plugin
+
+To get automatic type generation, you can use our new TypeScript plugin.
+
+๐ **Add the TypeScript plugin to `tsconfig.json`**
+
+```json
+{
+ "compilerOptions": {
+ "plugins": [{ "name": "@react-router/dev" }]
+ }
+}
+```
+
+We plan to add some other goodies to our TypeScript plugin soon, including:
+
+- Automatic `jsdoc` for route exports that include links to official docs
+- Autocomplete for route exports
+- Warnings for non-HMR compliant exports
+
+###### VSCode
+
+TypeScript looks for plugins registered in `tsconfig.json` in the local `node_modules/`,
+but VSCode ships with its own copy of TypeScript that is installed outside of your project.
+For TypeScript plugins to work, you'll need to tell VSCode to use the local workspace version of TypeScript.
+
+๐ **Ensure that VSCode is using the workspace version of TypeScript**
+
+This should already be set up for you by a `.vscode/settings.json`:
+
+```json
+{
+ "typescript.tsdk": "node_modules/typescript/lib"
+}
+```
+
+Alternatively, you can open up any TypeScript file and use CMD+SHIFT+P to find `Select TypeScript Version` and then select `Use Workspace Version`. You may need to quit VSCode and reopen it for this setting to take effect.
+
+###### Troubleshooting
+
+In VSCode, open up any TypeScript file in your project and then use CMD+SHIFT+P to select `Open TS Server log`. There should be a log for `[react-router] setup` that indicates that the plugin was resolved correctly. Then look for any errors in the log.
+
+#### Prerendering
+
+React Router v7 includes a new `prerender` config in the vite plugin to support SSG use-cases. This will pre-render your `.html` and `.data` files at build time and so you can serve them statically at runtime from a running server or a CDN ([#11539](https://github.com/remix-run/react-router/pull/11539))
+
+```ts
+export default defineConfig({
+ plugins: [
+ reactRouter({
+ async prerender({ getStaticPaths }) {
+ let slugs = await fakeGetSlugsFromCms();
+ return [
+ ...getStaticPaths(),
+ ...slugs.map((slug) => `/product/${slug}`),
+ ];
+ },
+ }),
+ tsconfigPaths(),
+ ],
+});
+
+async function fakeGetSlugsFromCms() {
+ await new Promise((r) => setTimeout(r, 1000));
+ return ["shirt", "hat"];
+}
+```
+
+### Major Changes (`react-router`)
+
+- Remove the original `defer` implementation in favor of using raw promises via single fetch and `turbo-stream` ([#11744](https://github.com/remix-run/react-router/pull/11744))
+ - This removes these exports from React Router:
+ - `defer`
+ - `AbortedDeferredError`
+ - `type TypedDeferredData`
+ - `UNSAFE_DeferredData`
+ - `UNSAFE_DEFERRED_SYMBOL`
+- Collapse packages into `react-router`([#11505](https://github.com/remix-run/react-router/pull/11505))
+ - `@remix-run/router`
+ - `react-router-dom`
+ - `@remix-run/server-runtime`
+ - `@remix-run/testing`
+ - As a note, the `react-router-dom` package is maintained to ease adoption but it simply re-exports all APIs from `react-router`
+- Drop support for Node 16, React Router SSR now requires Node 18 or higher ([#11391](https://github.com/remix-run/react-router/pull/11391), [#11690](https://github.com/remix-run/react-router/pull/11690))
+- Remove `future.v7_startTransition` flag ([#11696](https://github.com/remix-run/react-router/pull/11696))
+- Expose the underlying router promises from the following APIs for composition in React 19 APIs: ([#11521](https://github.com/remix-run/react-router/pull/11521))
+- Remove `future.v7_normalizeFormMethod` future flag ([#11697](https://github.com/remix-run/react-router/pull/11697))
+- Imports/Exports cleanup ([#11840](https://github.com/remix-run/react-router/pull/11840))
+ - Removed the following exports that were previously public API from `@remix-run/router`
+ - types
+ - `AgnosticDataIndexRouteObject`
+ - `AgnosticDataNonIndexRouteObject`
+ - `AgnosticDataRouteMatch`
+ - `AgnosticDataRouteObject`
+ - `AgnosticIndexRouteObject`
+ - `AgnosticNonIndexRouteObject`
+ - `AgnosticRouteMatch`
+ - `AgnosticRouteObject`
+ - `TrackedPromise`
+ - `unstable_AgnosticPatchRoutesOnMissFunction`
+ - `Action` -> exported as `NavigationType` via `react-router`
+ - `Router` exported as `RemixRouter` to differentiate from RR's ``
+ - API
+ - `getToPathname` (`@private`)
+ - `joinPaths` (`@private`)
+ - `normalizePathname` (`@private`)
+ - `resolveTo` (`@private`)
+ - `stripBasename` (`@private`)
+ - `createBrowserHistory` -> in favor of `createBrowserRouter`
+ - `createHashHistory` -> in favor of `createHashRouter`
+ - `createMemoryHistory` -> in favor of `createMemoryRouter`
+ - `createRouter`
+ - `createStaticHandler` -> in favor of wrapper `createStaticHandler` in RR Dom
+ - `getStaticContextFromError`
+ - Removed the following exports that were previously public API from `react-router`
+ - `Hash`
+ - `Pathname`
+ - `Search`
+- Remove `future.v7_prependBasename` from the internalized `@remix-run/router` package ([#11726](https://github.com/remix-run/react-router/pull/11726))
+- Remove `future.v7_throwAbortReason` from internalized `@remix-run/router` package ([#11728](https://github.com/remix-run/react-router/pull/11728))
+- Add `exports` field to all packages ([#11675](https://github.com/remix-run/react-router/pull/11675))
+- Renamed `RemixContext` to `FrameworkContext` ([#11705](https://github.com/remix-run/react-router/pull/11705))
+- Update the minimum React version to 18 ([#11689](https://github.com/remix-run/react-router/pull/11689))
+- `PrefetchPageDescriptor` replaced by `PageLinkDescriptor` ([#11960](https://github.com/remix-run/react-router/pull/11960))
+- Remove the `future.v7_partialHydration` flag ([#11725](https://github.com/remix-run/react-router/pull/11725))
+ - This also removes the `` prop
+ - To migrate, move the `fallbackElement` to a `hydrateFallbackElement`/`HydrateFallback` on your root route
+ - Also worth nothing there is a related breaking changer with this future flag:
+ - Without `future.v7_partialHydration` (when using `fallbackElement`), `state.navigation` was populated during the initial load
+ - With `future.v7_partialHydration`, `state.navigation` remains in an `"idle"` state during the initial load
+- Remove `future.v7_relativeSplatPath` future flag ([#11695](https://github.com/remix-run/react-router/pull/11695))
+- Remove remaining future flags ([#11820](https://github.com/remix-run/react-router/pull/11820))
+ - React Router `v7_skipActionErrorRevalidation`
+ - Remix `v3_fetcherPersist`, `v3_relativeSplatPath`, `v3_throwAbortReason`
+- Rename `createRemixStub` to `createRoutesStub` ([#11692](https://github.com/remix-run/react-router/pull/11692))
+- Remove `@remix-run/router` deprecated `detectErrorBoundary` option in favor of `mapRouteProperties` ([#11751](https://github.com/remix-run/react-router/pull/11751))
+- Add `react-router/dom` subpath export to properly enable `react-dom` as an optional `peerDependency` ([#11851](https://github.com/remix-run/react-router/pull/11851))
+ - This ensures that we don't blindly `import ReactDOM from "react-dom"` in `` in order to access `ReactDOM.flushSync()`, since that would break `createMemoryRouter` use cases in non-DOM environments
+ - DOM environments should import from `react-router/dom` to get the proper component that makes `ReactDOM.flushSync()` available:
+ - If you are using the Vite plugin, use this in your `entry.client.tsx`:
+ - `import { HydratedRouter } from 'react-router/dom'`
+ - If you are not using the Vite plugin and are manually calling `createBrowserRouter`/`createHashRouter`:
+ - `import { RouterProvider } from "react-router/dom"`
+- Remove `future.v7_fetcherPersist` flag ([#11731](https://github.com/remix-run/react-router/pull/11731))
+- Allow returning `undefined` from loaders and actions ([#11680](https://github.com/remix-run/react-router/pull/11680), [#12057]([https://github.com/remix-run/react-router/pull/1205))
+- Use `createRemixRouter`/`RouterProvider` in `entry.client` instead of `RemixBrowser` ([#11469](https://github.com/remix-run/react-router/pull/11469))
+- Remove the deprecated `json` utility ([#12146](https://github.com/remix-run/react-router/pull/12146))
+ - You can use [`Response.json`](https://developer.mozilla.org/en-US/docs/Web/API/Response/json_static) if you still need to construct JSON responses in your app
+
+### Major Changes (`@react-router/*`)
+
+- Remove `future.v3_singleFetch` flag ([#11522](https://github.com/remix-run/react-router/pull/11522))
+- Drop support for Node 16 and 18, update minimum Node version to 20 ([#11690](https://github.com/remix-run/react-router/pull/11690), [#12171](https://github.com/remix-run/react-router/pull/12171))
+ - Remove `installGlobals()` as this should no longer be necessary
+- Add `exports` field to all packages ([#11675](https://github.com/remix-run/react-router/pull/11675))
+- No longer re-export APIs from `react-router` through different runtime/adapter packages ([#11702](https://github.com/remix-run/react-router/pull/11702))
+- For Remix consumers migrating to React Router, the `crypto` global from the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) is now required when using cookie and session APIs
+ - This means that the following APIs are provided from `react-router` rather than platform-specific packages: ([#11837](https://github.com/remix-run/react-router/pull/11837))
+ - `createCookie`
+ - `createCookieSessionStorage`
+ - `createMemorySessionStorage`
+ - `createSessionStorage`
+ - For consumers running older versions of Node, the `installGlobals` function from `@remix-run/node` has been updated to define `globalThis.crypto`, using [Node's `require('node:crypto').webcrypto` implementation](https://nodejs.org/api/webcrypto.html)
+ - Since platform-specific packages no longer need to implement this API, the following low-level APIs have been removed:
+ - `createCookieFactory`
+ - `createSessionStorageFactory`
+ - `createCookieSessionStorageFactory`
+ - `createMemorySessionStorageFactory`
+- Consolidate types previously duplicated across `@remix-run/router`, `@remix-run/server-runtime`, and `@remix-run/react` now that they all live in `react-router` ([#12177](https://github.com/remix-run/react-router/pull/12177))
+ - Examples: `LoaderFunction`, `LoaderFunctionArgs`, `ActionFunction`, `ActionFunctionArgs`, `DataFunctionArgs`, `RouteManifest`, `LinksFunction`, `Route`, `EntryRoute`
+ - The `RouteManifest` type used by the "remix" code is now slightly stricter because it is using the former `@remix-run/router` `RouteManifest`
+ - `Record -> Record`
+ - Removed `AppData` type in favor of inlining `unknown` in the few locations it was used
+ - Removed `ServerRuntimeMeta*` types in favor of the `Meta*` types they were duplicated from
+- Migrate Remix v2 type generics to React Router ([#12180](https://github.com/remix-run/react-router/pull/12180))
+ - These generics are provided for Remix v2 migration purposes
+ - These generics and the APIs they exist on should be considered informally deprecated in favor of the new `Route.*` types
+ - Anyone migrating from React Router v6 should probably not leverage these new generics and should migrate straight to the `Route.*` types
+ - For React Router v6 users, these generics are new and should not impact your app, with one exception
+ - `useFetcher` previously had an optional generic (used primarily by Remix v2) that expected the data type
+ - This has been updated in v7 to expect the type of the function that generates the data (i.e., `typeof loader`/`typeof action`)
+ - Therefore, you should update your usages:
+ - โ `useFetcher()`
+ - โ `useFetcher()`
+- Update `cookie` dependency to `^1.0.1` - please see the [release notes](https://github.com/jshttp/cookie/releases) for any breaking changes ([#12172](https://github.com/remix-run/react-router/pull/12172))
+- `@react-router/cloudflare` - For Remix consumers migrating to React Router, all exports from `@remix-run/cloudflare-pages` are now provided for React Router consumers in the `@react-router/cloudflare` package. There is no longer a separate package for Cloudflare Pages. ([#11801](https://github.com/remix-run/react-router/pull/11801))
+- `@react-router/cloudflare` - The `@remix-run/cloudflare-workers` package has been deprecated. Remix consumers migrating to React Router should use the `@react-router/cloudflare` package directly. For guidance on how to use `@react-router/cloudflare` within a Cloudflare Workers context, refer to the Cloudflare Workers template. ([#11801](https://github.com/remix-run/react-router/pull/11801))
+- `@react-router/dev` - For Remix consumers migrating to React Router, the `vitePlugin` and `cloudflareDevProxyVitePlugin` exports have been renamed and moved. ([#11904](https://github.com/remix-run/react-router/pull/11904))
+- `@react-router/dev` - For Remix consumers migrating to React Router who used the Vite plugin's `buildEnd` hook, the resolved `reactRouterConfig` object no longer contains a `publicPath` property since this belongs to Vite, not React Router ([#11575](https://github.com/remix-run/react-router/pull/11575))
+- `@react-router/dev` - For Remix consumers migrating to React Router, the Vite plugin's `manifest` option has been removed ([#11573](https://github.com/remix-run/react-router/pull/11573))
+- `@react-router/dev` - Update default `isbot` version to v5 and drop support for `isbot@3` ([#11770](https://github.com/remix-run/react-router/pull/11770))
+ - If you have `isbot@4` or `isbot@5` in your `package.json`:
+ - You do not need to make any changes
+ - If you have `isbot@3` in your `package.json` and you have your own `entry.server.tsx` file in your repo
+ - You do not need to make any changes
+ - You can upgrade to `isbot@5` independent of the React Router v7 upgrade
+ - If you have `isbot@3` in your `package.json` and you do not have your own `entry.server.tsx` file in your repo
+ - You are using the internal default entry provided by React Router v7 and you will need to upgrade to `isbot@5` in your `package.json`
+- `@react-router/dev` - For Remix consumers migrating to React Router, Vite manifests (i.e. `.vite/manifest.json`) are now written within each build subdirectory, e.g. `build/client/.vite/manifest.json` and `build/server/.vite/manifest.json` instead of `build/.vite/client-manifest.json` and `build/.vite/server-manifest.json`. This means that the build output is now much closer to what you'd expect from a typical Vite project. ([#11573](https://github.com/remix-run/react-router/pull/11573))
+ - Originally the Remix Vite plugin moved all Vite manifests to a root-level `build/.vite` directory to avoid accidentally serving them in production, particularly from the client build. This was later improved with additional logic that deleted these Vite manifest files at the end of the build process unless Vite's `build.manifest` had been enabled within the app's Vite config. This greatly reduced the risk of accidentally serving the Vite manifests in production since they're only present when explicitly asked for. As a result, we can now assume that consumers will know that they need to manage these additional files themselves, and React Router can safely generate a more standard Vite build output.
+
+### Minor Changes
+
+- `react-router` - Params, loader data, and action data as props for route component exports ([#11961](https://github.com/remix-run/react-router/pull/11961))
+- `react-router` - Add route module type generation ([#12019](https://github.com/remix-run/react-router/pull/12019))
+- `react-router` - Remove duplicate `RouterProvider` implementations ([#11679](https://github.com/remix-run/react-router/pull/11679))
+- `react-router` - Stabilize `unstable_dataStrategy` ([#11969](https://github.com/remix-run/react-router/pull/11969))
+- `react-router` - Stabilize `unstable_patchRoutesOnNavigation` ([#11970](https://github.com/remix-run/react-router/pull/11970))
+- `react-router` - Add prefetching support to `Link`/`NavLink` when using Remix SSR ([#11402](https://github.com/remix-run/react-router/pull/11402))
+- `react-router` - Enhance `ScrollRestoration` so it can restore properly on an SSR'd document load ([#11401](https://github.com/remix-run/react-router/pull/11401))
+- `@react-router/dev` - Add support for the `prerender` config in the React Router vite plugin, to support existing SSG use-cases ([#11539](https://github.com/remix-run/react-router/pull/11539))
+- `@react-router/dev` - Remove internal `entry.server.spa.tsx` implementation which was not compatible with the Single Fetch async hydration approach ([#11681](https://github.com/remix-run/react-router/pull/11681))
+- `@react-router/serve`: Update `express.static` configurations to support new `prerender` API ([#11547](https://github.com/remix-run/react-router/pull/11547))
+ - Assets in the `build/client/assets` folder are served as before, with a 1-year immutable `Cache-Control` header
+ - Static files outside of assets, such as pre-rendered `.html` and `.data` files are not served with a specific `Cache-Control` header
+ - `.data` files are served with `Content-Type: text/x-turbo`
+ - For some reason, when adding this via `express.static`, it seems to also add a `Cache-Control: public, max-age=0` to `.data` files
+
+### Patch Changes
+
+- Replace `substr` with `substring` ([#12080](https://github.com/remix-run/react-router/pull/12080))
+- `react-router` - Fix redirects returned from loaders/actions using `data()` ([#12021](https://github.com/remix-run/react-router/pull/12021))
+- `@react-router/dev` - Enable prerendering for resource routes ([#12200](https://github.com/remix-run/react-router/pull/12200))
+- `@react-router/dev` - resolve config directory relative to flat output file structure ([#12187](https://github.com/remix-run/react-router/pull/12187))
+
+### Changes by Package
+
+- [`react-router`](https://github.com/remix-run/react-router/blob/react-router%407.0.0/packages/react-router/CHANGELOG.md#700)
+- [`@react-router/architect`](https://github.com/remix-run/react-router/blob/react-router%407.0.0/packages/react-router-architect/CHANGELOG.md#700)
+- [`@react-router/cloudflare`](https://github.com/remix-run/react-router/blob/react-router%407.0.0/packages/react-router-cloudflare/CHANGELOG.md#700)
+- [`@react-router/dev`](https://github.com/remix-run/react-router/blob/react-router%407.0.0/packages/react-router-dev/CHANGELOG.md#700)
+- [`@react-router/express`](https://github.com/remix-run/react-router/blob/react-router%407.0.0/packages/react-router-express/CHANGELOG.md#700)
+- [`@react-router/fs-routes`](https://github.com/remix-run/react-router/blob/react-router%407.0.0/packages/react-router-fs-routes/CHANGELOG.md#700)
+- [`@react-router/node`](https://github.com/remix-run/react-router/blob/react-router%407.0.0/packages/react-router-node/CHANGELOG.md#700)
+- [`@react-router/remix-config-routes-adapter`](https://github.com/remix-run/react-router/blob/react-router%407.0.0/packages/react-router-remix-config-routes-adapter/CHANGELOG.md#700)
+- [`@react-router/serve`](https://github.com/remix-run/react-router/blob/react-router%407.0.0/packages/react-router-serve/CHANGELOG.md#700)
+
+# **Full Changelog**: [`v6.28.0...v7.0.0`](https://github.com/remix-run/react-router/compare/react-router@6.28.0...react-router@7.0.0)
+
+> > > > > > > release-next
+
## v6.28.0
Date: 2024-11-06
@@ -236,6 +815,12 @@ Date: 2024-11-06
**Full Changelog**: [`v6.27.0...v6.28.0`](https://github.com/remix-run/react-router/compare/react-router@6.27.0...react-router@6.28.0)
+# <<<<<<< HEAD
+
+> > > > > > > dev
+
+> > > > > > > release-next
+
## v6.27.0
Date: 2024-10-11
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a6e637d603..0bfb0d0d60 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1 +1 @@
-Please see [our guide to contributing](docs/guides/contributing.md).
+Please see [our guide to contributing](docs/community/contributing.md).
diff --git a/README.md b/README.md
index fa131202e2..ebc5397c2b 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
[build-badge]: https://img.shields.io/github/actions/workflow/status/remix-run/react-router/test.yml?branch=dev&style=square
[build]: https://github.com/remix-run/react-router/actions/workflows/test.yml
-React Router is a lightweight, fully-featured routing library for the [React](https://reactjs.org) JavaScript library. React Router runs everywhere that React runs; on the web, on the server (using node.js), and on React Native.
+React Router is a lightweight, fully-featured routing library for the [React](https://reactjs.org) JavaScript library. React Router runs anywhere React runs; on the web, on the server with node.js, or on any other Javascript platform that supports the [Web Fetch API][fetch-api].
If you're new to React Router, we recommend you start with [the tutorial](https://reactrouter.com/en/main/start/tutorial).
@@ -21,9 +21,12 @@ There are many different ways to contribute to React Router's development. If yo
This repository is a monorepo containing the following packages:
+- [`@react-router/dev`](/packages/react-router-dev)
+- [`@react-router/express`](/packages/react-router-express)
+- [`@react-router/node`](/packages/react-router-node)
+- [`@react-router/serve`](/packages/react-router-serve)
- [`react-router`](/packages/react-router)
- [`react-router-dom`](/packages/react-router-dom)
-- [`react-router-native`](/packages/react-router-native)
## Changes
@@ -36,3 +39,5 @@ You may provide financial support for this project by donating [via Open Collect
## About
React Router is developed and maintained by [Remix Software](https://remix.run) and many [amazing contributors](https://github.com/remix-run/react-router/graphs/contributors).
+
+[fetch-api]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
diff --git a/build.utils.ts b/build.utils.ts
new file mode 100644
index 0000000000..83cb783cc2
--- /dev/null
+++ b/build.utils.ts
@@ -0,0 +1,12 @@
+export function createBanner(packageName: string, version: string) {
+ return `/**
+ * ${packageName} v${version}
+ *
+ * Copyright (c) Remix Software Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.md file in the root directory of this source tree.
+ *
+ * @license MIT
+ */`;
+}
diff --git a/contributors.yml b/contributors.yml
index c7bd8beddc..a75046113d 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -15,12 +15,15 @@
- alany411
- alberto
- alexandernanberg
+- alexanderson1993
- alexlbr
- AmRo045
- amsal
+- andreasottosson-polestar
- andreiduca
- antonmontrezor
- appden
+- apple-yagi
- arjunyel
- arka1002
- Armanio
@@ -190,6 +193,7 @@
- mikib0
- minami-minami
- minthulim
+- mjackson
- mlewando
- modex98
- morleytatro
@@ -207,6 +211,7 @@
- OlegDev1
- omahs
- omar-moquete
+- OnurGvnc
- p13i
- parched
- parveen232
@@ -218,6 +223,7 @@
- promet99
- pyitphyoaung
- refusado
+- rifaidev
- rimian
- robbtraister
- RobHannay
@@ -226,6 +232,7 @@
- rubeonline
- ryanflorence
- ryanhiebert
+- saengmotmi
- sanketshah19
- saul-atomrigs
- sbolel
@@ -241,16 +248,20 @@
- SkayuX
- skratchdot
- smithki
+- soartec-lab
+- sorrycc
- souzasmatheus
- srmagura
- SsongQ-92
- stasundr
- stmtk1
+- sukvvon
- swalker326
- tanayv
- thecode00
- theostavrides
- thepedroferrari
+- thethmuu
- thisiskartik
- thomasverleye
- ThornWu
@@ -266,6 +277,7 @@
- triangularcube
- trungpv1601
- ttys026
+- Tumas2
- turansky
- tyankatsu0105
- underager
diff --git a/decisions/0001-use-npm-to-manage-npm-dependencies-for-deno-projects.md b/decisions/0001-use-npm-to-manage-npm-dependencies-for-deno-projects.md
new file mode 100644
index 0000000000..35a6ec0a36
--- /dev/null
+++ b/decisions/0001-use-npm-to-manage-npm-dependencies-for-deno-projects.md
@@ -0,0 +1,113 @@
+# Use `npm` to manage NPM dependencies for Deno projects
+
+Date: 2022-05-10
+
+Status: accepted
+
+## Context
+
+Deno has three ways to manage dependencies:
+
+1. Inlined URL imports: `import {...} from "https://deno.land/x/blah"`
+2. [deps.ts](https://deno.land/manual/examples/manage_dependencies)
+3. [Import maps](https://deno.land/manual/linking_to_external_code/import_maps)
+
+Additionally, NPM packages can be accessed as Deno modules via [Deno-friendly CDNs](https://deno.land/manual/node/cdns#deno-friendly-cdns) like https://esm.sh.
+
+Remix has some requirements around dependencies:
+
+- Remix treeshakes dependencies that are free of side-effects.
+- Remix sets the environment (dev/prod/test) across all code, including dependencies, at runtime via the `NODE_ENV` environment variable.
+- Remix depends on some NPM packages that should be specified as peer dependencies (notably, `react` and `react-dom`).
+
+### Treeshaking
+
+To optimize bundle size, Remix [treeshakes](https://esbuild.github.io/api/#tree-shaking) your app's code and dependencies.
+This also helps to separate browser code and server code.
+
+Under the hood, the Remix compiler uses [esbuild](https://esbuild.github.io).
+Like other bundlers, `esbuild` uses [`sideEffects` in `package.json` to determine when it is safe to eliminate unused imports](https://esbuild.github.io/api/#conditionally-injecting-a-file).
+
+Unfortunately, URL imports do not have a standard mechanism for marking packages as side-effect free.
+
+### Setting dev/prod/test environment
+
+Deno-friendly CDNs set the environment via a query parameter (e.g. `?dev`), not via an environment variable.
+That means changing environment requires changing the URL import in the source code.
+While you could use multiple import maps (`dev.json`, `prod.json`, etc...) to workaround this, import maps have other limitations:
+
+- standard tooling for managing import maps is not available
+- import maps are not composeable, so any dependencies that use import maps must be manually accounted for
+
+### Specifying peer dependencies
+
+Even if import maps were perfected, CDNs compile each dependency in isolation.
+That means that specifying peer dependencies becomes tedious and error-prone as the user needs to:
+
+- determine which dependencies themselves depend on `react` (or other similar peer dependency), even if indirectly.
+- manually figure out which `react` version works across _all_ of these dependencies
+- set that version for `react` as a query parameter in _all_ of the URLs for the identified dependencies
+
+If any dependencies change (added, removed, version change),
+the user must repeat all of these steps again.
+
+## Decision
+
+### Use `npm` to manage NPM dependencies for Deno
+
+Do not use Deno-friendly CDNs for NPM dependencies in Remix projects using Deno.
+
+Use `npm` and `node_modules/` to manage NPM dependencies like `react` for Remix projects, even when using Deno with Remix.
+
+Deno module dependencies (e.g. from `https://deno.land`) can still be managed via URL imports.
+
+### Allow URL imports
+
+Remix will preserve any URL imports in the built bundles as external dependencies,
+letting your browser runtime and server runtime handle them accordingly.
+That means that you may:
+
+- use URL imports for the browser
+- use URL imports for the server, if your server runtime supports it
+
+For example, Node will throw errors for URL imports, while Deno will resolve URL imports as normal.
+
+### Do not support import maps
+
+Remix will not yet support import maps.
+
+## Consequences
+
+- URL imports will not be treeshaken.
+- Users can specify environment via the `NODE_ENV` environment variable at runtime.
+- Users won't have to do error-prone, manual dependency resolution.
+
+### VS Code type hints
+
+Users may configure an import map for the [Deno extension for VS Code](denoland.vscode-deno) to enable type hints for NPM-managed dependencies within their Deno editor:
+
+`.vscode/resolve_npm_imports_in_deno.json`
+
+```json
+{
+ "// This import map is used solely for the denoland.vscode-deno extension.": "",
+ "// Remix does not support import maps.": "",
+ "// Dependency management is done through `npm` and `node_modules/` instead.": "",
+ "// Deno-only dependencies may be imported via URL imports (without using import maps).": "",
+
+ "imports": {
+ "react": "https://esm.sh/react@18.0.0",
+ "react-dom": "https://esm.sh/react-dom@18.0.0",
+ "react-dom/server": "https://esm.sh/react-dom@18.0.0/server"
+ }
+}
+```
+
+`.vscode/settings.json`
+
+```json
+{
+ "deno.enable": true,
+ "deno.importMap": "./.vscode/resolve_npm_imports_in_deno.json"
+}
+```
diff --git a/decisions/0002-do-not-clone-request.md b/decisions/0002-do-not-clone-request.md
new file mode 100644
index 0000000000..30f599f7f0
--- /dev/null
+++ b/decisions/0002-do-not-clone-request.md
@@ -0,0 +1,19 @@
+# Do not clone request
+
+Date: 2022-05-13
+
+Status: accepted
+
+## Context
+
+To allow multiple loaders / actions to read the body of a request, we have been cloning the request before forwarding it to user-code. This is not the best thing to do as some runtimes will begin buffering the body to allow for multiple consumers. It also goes against "the platform" that states a request body should only be consumed once.
+
+## Decision
+
+Do not clone requests before they are passed to user-code (actions, handleDocumentRequest, handleDataRequest), and remove body from request passed to loaders. Loaders should be thought of as a "GET" / "HEAD" request handler. These request methods are not allowed to have a body, therefore you should not be reading it in your Remix loader function.
+
+## Consequences
+
+Loaders always receive a null body for the request.
+
+If you are reading the request body in both an action and handleDocumentRequest or handleDataRequest this will now fail as the body will have already been read. If you wish to continue reading the request body in multiple places for a single request against recommendations, consider using `.clone()` before reading it; just know this comes with tradeoffs.
diff --git a/decisions/0003-infer-types-for-useloaderdata-and-useactiondata-from-loader-and-action-via-generics.md b/decisions/0003-infer-types-for-useloaderdata-and-useactiondata-from-loader-and-action-via-generics.md
new file mode 100644
index 0000000000..c86a9c818f
--- /dev/null
+++ b/decisions/0003-infer-types-for-useloaderdata-and-useactiondata-from-loader-and-action-via-generics.md
@@ -0,0 +1,230 @@
+# Infer types for `useLoaderData` and `useActionData` from `loader` and `action` via generics
+
+Date: 2022-07-11
+
+Status: Superseded by [#0012](./0012-type-inference.md)
+
+## Context
+
+Goal: End-to-end type safety for `useLoaderData` and `useActionData` with great Developer Experience (DX)
+
+Related discussions:
+
+- [remix-run/remix#1254](https://github.com/remix-run/remix/pull/1254)
+- [remix-run/remix#3276](https://github.com/remix-run/remix/pull/3276)
+
+---
+
+In Remix v1.6.4, types for both `useLoaderData` and `useActionData` are parameterized with a generic:
+
+```tsx
+type MyLoaderData = {
+ /* ... */
+};
+type MyActionData = {
+ /* ... */
+};
+
+export default function Route() {
+ const loaderData = useLoaderData();
+ const actionData = useActionData();
+ return
{/* ... */}
;
+}
+```
+
+For end-to-end type safety, it is then the user's responsability to make sure that `loader` and `action` also use the same type in the `json` generic:
+
+```ts
+export const loader: LoaderFunction = () => {
+ return json({
+ /* ... */
+ });
+};
+
+export const action: ActionFunction = () => {
+ return json({
+ /* ... */
+ });
+};
+```
+
+### Diving into `useLoaderData`'s and `useActionData`'s generics
+
+Tracing through the `@remix-run/react` source code (v1.6.4), you'll find that `useLoaderData` returns an `any` type that is implicitly type cast to whatever type gets passed into the `useLoaderData` generic:
+
+```ts
+// https://github.com/remix-run/remix/blob/v1.6.4/packages/remix-react/components.tsx#L1370
+export function useLoaderData(): T {
+ return useRemixRouteContext().data; //
+}
+
+// https://github.com/remix-run/remix/blob/v1.6.4/packages/remix-react/components.tsx#L73
+function useRemixRouteContext(): RemixRouteContextType {
+ /* ... */
+}
+
+// https://github.com/remix-run/remix/blob/v1.6.4/packages/remix-react/components.tsx#L56
+interface RemixRouteContextType {
+ data: AppData;
+ id: string;
+}
+
+// https://github.com/remix-run/remix/blob/v1.6.4/packages/remix-react/data.ts#L4
+export type AppData = any;
+```
+
+Boiling this down, the code looks like:
+
+```ts
+let data: any;
+
+// somewhere else, `loader` gets called an sets `data` to some value
+
+function useLoaderData(): T {
+ return data; // <-- Typescript casts this `any` to `T`
+}
+```
+
+`useLoaderData` isn't basing its return type on how `data` was set (i.e. the return value of `loader`) nor is it validating the data.
+It's just blindly casting `data` to whatever the user passed in for the generic `T`.
+
+### Issues with current approach
+
+The developer experience is subpar.
+Users are required to write redundant code for the data types that could have been inferred from the arguments to `json`.
+Changes to the data shape require changing _both_ the declared `type` or `interface` as well as the argument to `json`.
+
+Additionally, the current approach encourages users to pass the same type to `json` with the `loader` and to `useLoaderData`, but **this is a footgun**!
+`json` can accept data types like `Date` that are JSON serializable, but `useLoaderData` will return the _serialized_ type:
+
+```ts
+type MyLoaderData = {
+ birthday: Date;
+};
+
+export const loader: LoaderFunction = () => {
+ return json({ birthday: new Date("February 15, 1992") });
+};
+
+export default function Route() {
+ const { birthday } = useLoaderData();
+ // ^ `useLoaderData` tricks Typescript into thinking this is a `Date`, when in fact its a `string`!
+}
+```
+
+Again, the same goes for `useActionData`.
+
+### Solution criteria
+
+- Return type of `useLoaderData` and `useActionData` should somehow be inferred from `loader` and `action`, not blindly type cast
+- Return type of `loader` and `action` should be inferred
+ - Necessarily, return type of `json` should be inferred from its input
+- No module side-effects (so higher-order functions like `makeLoader` is definitely a no).
+- `json` should allow everything that `JSON.stringify` allows.
+- `json` should allow only what `JSON.stringify` allows.
+- `useLoaderData` should not return anything that `JSON.parse` can't return.
+
+### Key insight: `loader` and `action` are an _implicit_ inputs
+
+While there's been interest in inferring the types for `useLoaderData` based on `loader`, there was [hesitance to use a Typescript generic to do so](https://github.com/remix-run/remix/pull/3276#issuecomment-1164764821).
+Typescript generics are apt for specifying or inferring types for _inputs_, not for blindly type casting output types.
+
+A key factor in the decision was identifying that `loader` and `action` are _implicit_ inputs of `useLoaderData` and `useActionData`.
+
+In other words, if `loader` and `useLoaderData` were guaranteed to run in the same process (and not cross the network), then we could write `useLoaderData(loader)`, specifying `loader` as an explicit input for `useLoaderData`.
+
+```ts
+// _conceptually_ `loader` is an input for `useLoaderData`
+function useLoaderData(loader: Loader) {
+ /*...*/
+}
+```
+
+Though `loader` and `useLoaderData` exist together in the same file at development-time, `loader` does not exist at runtime in the browser.
+Without the `loader` argument to infer types from, `useLoaderData` needs a way to learn about `loader`'s type at compile-time.
+
+Additionally, `loader` and `useLoaderData` are both managed by Remix across the network.
+While its true that Remix doesn't "own" the network in the strictest sense, having `useLoaderData` return data that does not correspond to its `loader` is an exceedingly rare edge-case.
+
+Same goes for `useActionData`.
+
+---
+
+A similar case is how [Prisma](https://www.prisma.io/) infers types from database schemas available at runtime, even though there are (exceedingly rare) edge-cases where that database schema _could_ be mutated after compile-time but before run-time.
+
+## Decision
+
+Explicitly provide type of the implicit `loader` input for `useLoaderData` and then infer the return type for `useLoaderData`.
+Do the same for `action` and `useActionData`.
+
+```ts
+export const loader = async (args: LoaderArgs) => {
+ // ...
+ return json(/*...*/);
+};
+
+export default function Route() {
+ const data = useLoaderData();
+ // ...
+}
+```
+
+Additionally, the inferred return type for `useLoaderData` will only include serializable (JSON) types.
+
+### Return `unknown` when generic is omitted
+
+Omitting the generic for `useLoaderData` or `useActionData` results in `any` being returned.
+This hides potential type errors from the user.
+Instead, we'll change the return type to `unknown`.
+
+```ts
+type MyLoaderData = {
+ /*...*/
+};
+
+export default function Route() {
+ const data = useLoaderData();
+ // ^? unknown
+}
+```
+
+Note: Since this would be a breaking change, changing the return type to `unknown` will be slated for v2.
+
+### Deprecate non-inferred types via generics
+
+Passing in a non-inferred type for `useLoaderData` is hiding an unsafe type cast.
+Using the `useLoaderData` in this way will be deprecated in favor of an explicit type cast that clearly communicates the assumptions being made:
+
+```ts
+type MyLoaderData = {
+ /*...*/
+};
+
+export default function Route() {
+ const dataGeneric = useLoaderData(); // <-- will be deprecated
+ const dataCast = useLoaderData() as MyLoaderData; // <- use this instead
+}
+```
+
+## Consequences
+
+- Users can continue to provide non-inferred types by type casting the result of `useLoaderData` or `useActionData`
+- Users can opt-in to inferred types by using `typeof loader` or `typeof action` at the generic for `useLoaderData` or `useActionData`.
+- Return types for `loader` and `action` will be the sources-of-truth for the types inferred for `useLoaderData` and `useActionData`.
+- Users do not need to write redundant code to align types across the network
+- Return type of `useLoaderData` and `useActionData` will correspond to the JSON _serialized_ types from `json` calls in `loader` and `action`, eliminating a class of errors.
+- `LoaderFunction` and `ActionFunction` should not be used when opting into type inference as they override the inferred return types.[^1]
+
+๐จ Users who opt-in to inferred types **MUST** return a `TypedResponse` from `json` and **MUST NOT** return a bare object:
+
+```ts
+const loader = () => {
+ // NO
+ return { hello: "world" };
+
+ // YES
+ return json({ hello: "world" });
+};
+```
+
+[^1]: The proposed `satisfies` operator for Typescript would let `LoaderFunction` and `ActionFunction` enforce function types while preserving the narrower inferred return type: https://github.com/microsoft/TypeScript/issues/47920
diff --git a/decisions/0004-streaming-apis.md b/decisions/0004-streaming-apis.md
new file mode 100644
index 0000000000..5677a48e36
--- /dev/null
+++ b/decisions/0004-streaming-apis.md
@@ -0,0 +1,193 @@
+---
+title: Remix (and React Router) Streaming APIs
+---
+
+# Title
+
+Date: 2022-07-27
+
+Status: accepted
+
+## Context
+
+Remix aims to provide first-class support for React 18's streaming capabilities. Throughout the development process we went through many iterations and naming schemes around the APIs we plan to build into Remix to support streaming, so this document aims to lay out the final names we chose and the reasons behind it.
+
+It's also worth nothing that even in a single-page-application without SSR-streaming, the same concepts still apply so these decisions were made with React Router 6.4.0 in mind as well - which will support the same Data APIs from Remix.
+
+## Decision
+
+Streaming in Remix can be thought of as having 3 touch points with corresponding APIs:
+
+1. _Initiating_ a streamed response in your `loader` can be done by returning a `defer(object)` call from your `loader` in which some of the keys on `object` are `Promise` instances
+2. _Accessing_ a streamed response from `useLoaderData`
+ 1. No new APIs here - when you return a `defer()` response from your loader, you'll get `Promise` values inside your `useLoaderData` object ๐
+3. _Rendering_ a streamed value (with fallback and error handling) in your component
+ 1. You can render a `Promise` from `useLoaderData()` with the `` component
+ 2. `` accepts an `errorElement` prop to handle error UI
+ 3. `` should be wrapped with a `` component to handle your loading UI
+
+## Details
+
+In the spirit of `#useThePlatform` we've chosen to leverage the `Promise` API to represent these "eventually available" values. When Remix receives a `defer()` response back from a `loader`, it needs to serialize that `Promise` over the network to the client application (prompting Jacob to coin the phrase [_"promise teleportation over the network"_][promise teleportation] ๐ฅ).
+
+### Initiating
+
+In order to initiate a streamed response in your `loader`, you can use the `defer()` utility which accepts a JSON object with `Promise` values from your `loader`.
+
+```tsx
+export async function loader() {
+ return defer({
+ // Await this, don't stream
+ critical: await fetchCriticalData(),
+ // Don't await this - stream it!
+ lazy: fetchLazyData(),
+ });
+}
+```
+
+By not using `await` on `fetchLazyData()` Remix knows that this value is not ready yet _but eventually will be_ and therefore Remix will leverage a streamed HTTP response allowing it to send up the resolved/rejected value when available. Essentially serializing/teleporting that Promise over the network via a streamed HTTP response.
+
+Just like `json()`, the `defer()` will accept a second optional `responseInit` param that lets you customize the resulting `Response` (i.e., in case you need to set custom headers).
+
+The name `defer` was settled on as a corollary to `
+