diff --git a/.changeset/afraid-bears-fry.md b/.changeset/afraid-bears-fry.md
deleted file mode 100644
index 034ccdc6a69..00000000000
--- a/.changeset/afraid-bears-fry.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-"@remix-run/dev": patch
-"@remix-run/express": patch
-"@remix-run/serve": patch
----
-
-Upgrade `express` dependency to `^4.19.2`
diff --git a/.changeset/forty-gifts-boil.md b/.changeset/forty-gifts-boil.md
deleted file mode 100644
index e44c5b50e44..00000000000
--- a/.changeset/forty-gifts-boil.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@remix-run/server-runtime": patch
----
-
-Properly handle thrown 4xx/5xx response stubs in single fetch
diff --git a/.changeset/heavy-ways-rule.md b/.changeset/heavy-ways-rule.md
deleted file mode 100644
index f284fd575f3..00000000000
--- a/.changeset/heavy-ways-rule.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@remix-run/react": patch
----
-
-Don't prefetch server loader data when clientLoader exists
diff --git a/.changeset/mighty-experts-agree.md b/.changeset/mighty-experts-agree.md
deleted file mode 100644
index 707af186750..00000000000
--- a/.changeset/mighty-experts-agree.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@remix-run/react": patch
----
-
-Avoid hydration loops when `Layout` `ErrorBoundary` renders also throw
diff --git a/.changeset/neat-ghosts-thank.md b/.changeset/neat-ghosts-thank.md
deleted file mode 100644
index 82d6992aa0d..00000000000
--- a/.changeset/neat-ghosts-thank.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@remix-run/server-runtime": patch
----
-
-Change single fetch redirects to use a 202 status to avoid automatic caching
diff --git a/.changeset/shiny-bags-breathe.md b/.changeset/shiny-bags-breathe.md
deleted file mode 100644
index d10ef00a1b0..00000000000
--- a/.changeset/shiny-bags-breathe.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@remix-run/react": patch
----
-
-Fix a bug where hydration wouldn't work right when using child routes and hydrate fallbacks with a basename
diff --git a/.changeset/smart-mirrors-flow.md b/.changeset/smart-mirrors-flow.md
deleted file mode 100644
index ffd88d253af..00000000000
--- a/.changeset/smart-mirrors-flow.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-"@remix-run/server-runtime": patch
----
-
-- Fix error when returning null from a resource route in single fetch
-- Fix issues with returning or throwing a response stub from a resource route in single fetch
diff --git a/.changeset/strange-chefs-think.md b/.changeset/strange-chefs-think.md
deleted file mode 100644
index 201d8f4eec0..00000000000
--- a/.changeset/strange-chefs-think.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-"@remix-run/react": patch
-"@remix-run/server-runtime": patch
----
-
-Update to `turbo-stream@2.2.0` for single fetch
diff --git a/.changeset/two-jars-help.md b/.changeset/two-jars-help.md
deleted file mode 100644
index ae4c9a6710f..00000000000
--- a/.changeset/two-jars-help.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-"@remix-run/react": minor
-"@remix-run/server-runtime": minor
----
-
-Add support for Lazy Route Discovery (a.k.a. Fog of War)
-
-- RFC: https://github.com/remix-run/react-router/discussions/11113
-- Docs: https://remix.run/docs/guides/fog-of-war
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a79b9fbdfe5..f63cda0d8fd 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -81,8 +81,8 @@ jobs:
- name: ⎔ Setup node
uses: actions/setup-node@v4
with:
- node-version: 18
- cache: "npm"
+ node-version-file: ".nvmrc"
+ cache: "pnpm"
- id: find_package_version
run: |
@@ -98,26 +98,26 @@ jobs:
needs: [release, find_package_version]
uses: ./.github/workflows/release-comments.yml
- deployments:
- name: 🚀 Deployment Tests
- if: github.repository == 'remix-run/remix'
- needs: [release, find_package_version]
- uses: ./.github/workflows/deployments.yml
- secrets:
- TEST_AWS_ACCESS_KEY_ID: ${{ secrets.TEST_AWS_ACCESS_KEY_ID }}
- TEST_AWS_SECRET_ACCESS_KEY: ${{ secrets.TEST_AWS_SECRET_ACCESS_KEY }}
- TEST_CF_ACCOUNT_ID: ${{ secrets.TEST_CF_ACCOUNT_ID }}
- TEST_CF_GLOBAL_API_KEY: ${{ secrets.TEST_CF_GLOBAL_API_KEY }}
- TEST_CF_EMAIL: ${{ secrets.TEST_CF_EMAIL }}
- TEST_CF_PAGES_API_TOKEN: ${{ secrets.TEST_CF_PAGES_API_TOKEN }}
- TEST_CF_API_TOKEN: ${{ secrets.TEST_CF_API_TOKEN }}
- TEST_DENO_DEPLOY_TOKEN: ${{ secrets.TEST_DENO_DEPLOY_TOKEN }}
- TEST_FLY_TOKEN: ${{ secrets.TEST_FLY_TOKEN }}
+ # deployments:
+ # name: 🚀 Deployment Tests
+ # if: github.repository == 'remix-run/remix'
+ # needs: [release, find_package_version]
+ # uses: ./.github/workflows/deployments.yml
+ # secrets:
+ # TEST_AWS_ACCESS_KEY_ID: ${{ secrets.TEST_AWS_ACCESS_KEY_ID }}
+ # TEST_AWS_SECRET_ACCESS_KEY: ${{ secrets.TEST_AWS_SECRET_ACCESS_KEY }}
+ # TEST_CF_ACCOUNT_ID: ${{ secrets.TEST_CF_ACCOUNT_ID }}
+ # TEST_CF_GLOBAL_API_KEY: ${{ secrets.TEST_CF_GLOBAL_API_KEY }}
+ # TEST_CF_EMAIL: ${{ secrets.TEST_CF_EMAIL }}
+ # TEST_CF_PAGES_API_TOKEN: ${{ secrets.TEST_CF_PAGES_API_TOKEN }}
+ # TEST_CF_API_TOKEN: ${{ secrets.TEST_CF_API_TOKEN }}
+ # TEST_DENO_DEPLOY_TOKEN: ${{ secrets.TEST_DENO_DEPLOY_TOKEN }}
+ # TEST_FLY_TOKEN: ${{ secrets.TEST_FLY_TOKEN }}
- stacks:
- name: 🥞 Remix Stacks Test
- if: github.repository == 'remix-run/remix'
- needs: [release, find_package_version]
- uses: ./.github/workflows/stacks.yml
- with:
- version: ${{ needs.find_package_version.outputs.package_version }}
+ # stacks:
+ # name: 🥞 Remix Stacks Test
+ # if: github.repository == 'remix-run/remix'
+ # needs: [release, find_package_version]
+ # uses: ./.github/workflows/stacks.yml
+ # with:
+ # version: ${{ needs.find_package_version.outputs.package_version }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56fe00d0b2d..fc4dde8d228 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,109 +13,116 @@ We manage release notes in this file instead of the paginated Github Releases Pa
Table of Contents
- [Remix Releases](#remix-releases)
- - [v2.9.2](#v292)
+ - [v2.10.0](#v2100)
- [What's Changed](#whats-changed)
- - [Updated Type-Safety for Single Fetch](#updated-type-safety-for-single-fetch)
+ - [Lazy Route Discovery (a.k.a. Fog of War)](#lazy-route-discovery-aka-fog-of-war)
+ - [Minor Changes](#minor-changes)
- [Patch Changes](#patch-changes)
- [Updated Dependencies](#updated-dependencies)
- [Changes by Package](#changes-by-package)
- - [v2.9.1](#v291)
+ - [v2.9.2](#v292)
+ - [What's Changed](#whats-changed-1)
+ - [Updated Type-Safety for Single Fetch](#updated-type-safety-for-single-fetch)
- [Patch Changes](#patch-changes-1)
+ - [Updated Dependencies](#updated-dependencies-1)
- [Changes by Package](#changes-by-package-1)
+ - [v2.9.1](#v291)
+ - [Patch Changes](#patch-changes-2)
+ - [Changes by Package](#changes-by-package-2)
- [v2.9.0](#v290)
- - [What's Changed](#whats-changed-1)
+ - [What's Changed](#whats-changed-2)
- [Single Fetch (unstable)](#single-fetch-unstable)
- [Undici](#undici)
- - [Minor Changes](#minor-changes)
- - [Patch Changes](#patch-changes-2)
- - [Updated Dependencies](#updated-dependencies-1)
- - [Changes by Package](#changes-by-package-2)
- - [v2.8.1](#v281)
+ - [Minor Changes](#minor-changes-1)
- [Patch Changes](#patch-changes-3)
- [Updated Dependencies](#updated-dependencies-2)
- [Changes by Package](#changes-by-package-3)
- - [v2.8.0](#v280)
- - [Minor Changes](#minor-changes-1)
+ - [v2.8.1](#v281)
- [Patch Changes](#patch-changes-4)
- [Updated Dependencies](#updated-dependencies-3)
- [Changes by Package](#changes-by-package-4)
- - [2.7.2](#272)
+ - [v2.8.0](#v280)
+ - [Minor Changes](#minor-changes-2)
- [Patch Changes](#patch-changes-5)
- - [2.7.1](#271)
+ - [Updated Dependencies](#updated-dependencies-4)
+ - [Changes by Package](#changes-by-package-5)
+ - [2.7.2](#272)
- [Patch Changes](#patch-changes-6)
+ - [2.7.1](#271)
+ - [Patch Changes](#patch-changes-7)
- [v2.7.0](#v270)
- - [What's Changed](#whats-changed-2)
+ - [What's Changed](#whats-changed-3)
- [Stabilized Vite Plugin](#stabilized-vite-plugin)
- [New `Layout` Export](#new-layout-export)
- [Basename support](#basename-support)
- [Cloudflare Proxy as a Vite Plugin](#cloudflare-proxy-as-a-vite-plugin)
- - [Minor Changes](#minor-changes-2)
- - [Patch Changes](#patch-changes-7)
- - [Updated Dependencies](#updated-dependencies-4)
- - [Changes by Package](#changes-by-package-5)
- - [v2.6.0](#v260)
- - [What's Changed](#whats-changed-3)
- - [Unstable Vite Plugin updates](#unstable-vite-plugin-updates)
- [Minor Changes](#minor-changes-3)
- [Patch Changes](#patch-changes-8)
- [Updated Dependencies](#updated-dependencies-5)
- [Changes by Package](#changes-by-package-6)
- - [v2.5.1](#v251)
+ - [v2.6.0](#v260)
+ - [What's Changed](#whats-changed-4)
+ - [Unstable Vite Plugin updates](#unstable-vite-plugin-updates)
+ - [Minor Changes](#minor-changes-4)
- [Patch Changes](#patch-changes-9)
- [Updated Dependencies](#updated-dependencies-6)
- [Changes by Package](#changes-by-package-7)
- - [v2.5.0](#v250)
- - [What's Changed](#whats-changed-4)
- - [SPA Mode (unstable)](#spa-mode-unstable)
- - [Server Bundles (unstable)](#server-bundles-unstable)
- - [Minor Changes](#minor-changes-4)
+ - [v2.5.1](#v251)
- [Patch Changes](#patch-changes-10)
- [Updated Dependencies](#updated-dependencies-7)
- [Changes by Package](#changes-by-package-8)
- - [v2.4.1](#v241)
+ - [v2.5.0](#v250)
+ - [What's Changed](#whats-changed-5)
+ - [SPA Mode (unstable)](#spa-mode-unstable)
+ - [Server Bundles (unstable)](#server-bundles-unstable)
+ - [Minor Changes](#minor-changes-5)
- [Patch Changes](#patch-changes-11)
- [Updated Dependencies](#updated-dependencies-8)
- [Changes by Package](#changes-by-package-9)
+ - [v2.4.1](#v241)
+ - [Patch Changes](#patch-changes-12)
+ - [Updated Dependencies](#updated-dependencies-9)
+ - [Changes by Package](#changes-by-package-10)
- [v2.4.0](#v240)
- - [What's Changed](#whats-changed-5)
+ - [What's Changed](#whats-changed-6)
- [Client Data](#client-data)
- [`future.v3_relativeSplatPath`](#futurev3_relativesplatpath)
- [Vite Updates (Unstable)](#vite-updates-unstable)
- - [Minor Changes](#minor-changes-5)
- - [Patch Changes](#patch-changes-12)
- - [Updated Dependencies](#updated-dependencies-9)
- - [Changes by Package](#changes-by-package-10)
- - [v2.3.1](#v231)
+ - [Minor Changes](#minor-changes-6)
- [Patch Changes](#patch-changes-13)
- [Updated Dependencies](#updated-dependencies-10)
- [Changes by Package](#changes-by-package-11)
- - [v2.3.0](#v230)
- - [What's Changed](#whats-changed-6)
- - [Stabilized `useBlocker`](#stabilized-useblocker)
- - [`unstable_flushSync` API](#unstable_flushsync-api)
- - [Minor Changes](#minor-changes-6)
+ - [v2.3.1](#v231)
- [Patch Changes](#patch-changes-14)
- [Updated Dependencies](#updated-dependencies-11)
- [Changes by Package](#changes-by-package-12)
- - [v2.2.0](#v220)
+ - [v2.3.0](#v230)
- [What's Changed](#whats-changed-7)
- - [Vite!](#vite)
- - [New Fetcher APIs](#new-fetcher-apis)
- - [Persistence Future Flag](#persistence-future-flag)
+ - [Stabilized `useBlocker`](#stabilized-useblocker)
+ - [`unstable_flushSync` API](#unstable_flushsync-api)
- [Minor Changes](#minor-changes-7)
- [Patch Changes](#patch-changes-15)
- [Updated Dependencies](#updated-dependencies-12)
- [Changes by Package](#changes-by-package-13)
- - [v2.1.0](#v210)
+ - [v2.2.0](#v220)
- [What's Changed](#whats-changed-8)
- - [View Transitions](#view-transitions)
- - [Stable `createRemixStub`](#stable-createremixstub)
+ - [Vite!](#vite)
+ - [New Fetcher APIs](#new-fetcher-apis)
+ - [Persistence Future Flag](#persistence-future-flag)
- [Minor Changes](#minor-changes-8)
- [Patch Changes](#patch-changes-16)
- [Updated Dependencies](#updated-dependencies-13)
- [Changes by Package](#changes-by-package-14)
- - [v2.0.1](#v201)
+ - [v2.1.0](#v210)
+ - [What's Changed](#whats-changed-9)
+ - [View Transitions](#view-transitions)
+ - [Stable `createRemixStub`](#stable-createremixstub)
+ - [Minor Changes](#minor-changes-9)
- [Patch Changes](#patch-changes-17)
+ - [Updated Dependencies](#updated-dependencies-14)
+ - [Changes by Package](#changes-by-package-15)
+ - [v2.0.1](#v201)
+ - [Patch Changes](#patch-changes-18)
- [Changes by Package 🔗](#changes-by-package-)
- [v2.0.0](#v200)
- [Breaking Changes](#breaking-changes)
@@ -127,8 +134,8 @@ We manage release notes in this file instead of the paginated Github Releases Pa
- [Breaking Type Changes](#breaking-type-changes)
- [New Features](#new-features)
- [Other Notable Changes](#other-notable-changes)
- - [Updated Dependencies](#updated-dependencies-14)
- - [Changes by Package](#changes-by-package-15)
+ - [Updated Dependencies](#updated-dependencies-15)
+ - [Changes by Package](#changes-by-package-16)
@@ -176,6 +183,61 @@ Date: YYYY-MM-DD
-->
+## v2.10.0
+
+Date: 2024-06-25
+
+### What's Changed
+
+#### Lazy Route Discovery (a.k.a. "Fog of War")
+
+The "Fog of War" feature in Remix, now available through the `future.unstable_fogOfWar` flag, is an optimization to reduce the up front size of the Remix route manifest. In most scenarios the Remix route manifest isn't prohibitively large so as to impact initial perf metrics, but at scale we've found that some apps can generate large manifests that are expensive to download and execute on app startup.
+
+When Fog of War is enabled, Remix will only include the initially server-rendered routes in the manifest and then it will fetch manifest "patches" for outgoing links as the user navigates around. By default, to avoid waterfalls Remix fetches patches for all rendered links, so that in the ideal case they've already been patched in prior to being clicked. If a user clicks a link before this eager discovery completes, then a small waterfall will occur to first "discover" the route, and then navigate to the route.
+
+Enabling this flag should require no application code changes. For more information, please see the [documentation](https://remix.run/docs/guides/fog-of-war).
+
+### Minor Changes
+
+- Add support for Lazy Route Discovery (a.k.a. Fog of War) ([#9600](https://github.com/remix-run/remix/pull/9600), [#9619](https://github.com/remix-run/remix/pull/9619))
+
+### Patch Changes
+
+- `@remix-run/{dev|express|serve}` - Upgrade `express` dependency to `^4.19.2` ([#9184](https://github.com/remix-run/remix/pull/9184))
+- `@remix-run/react` - Don't prefetch server `loader` data when `clientLoader` exists ([#9580](https://github.com/remix-run/remix/pull/9580))
+- `@remix-run/react` - Avoid hydration loops when `Layout`/`ErrorBoundary` renders also throw ([#9566](https://github.com/remix-run/remix/pull/9566))
+- `@remix-run/react` - Fix a hydration bug when using child routes and `HydrateFallback` components with a `basename` ([#9584](https://github.com/remix-run/remix/pull/9584))
+- `@remix-run/{server-runtime|react}` - Single Fetch: Update to `turbo-stream@2.2.0` ([#9562](https://github.com/remix-run/remix/pull/9562))
+- `@remix-run/server-runtime` - Single Fetch: Properly handle thrown 4xx/5xx response stubs ([#9501](https://github.com/remix-run/remix/pull/9501))
+- `@remix-run/server-runtime` - Single Fetch: Change redirects to use a 202 status to avoid automatic caching ([#9564](https://github.com/remix-run/remix/pull/9564))
+- `@remix-run/server-runtime` - Single Fetch: Fix issues with returning or throwing a response stub from a resource route in single fetch ([#9488](https://github.com/remix-run/remix/pull/9488))
+- `@remix-run/server-runtime` - Single Fetch: Fix error when returning `null` from a resource route ([#9488](https://github.com/remix-run/remix/pull/9488))
+
+### Updated Dependencies
+
+- [`react-router-dom@6.24.0`](https://github.com/remix-run/react-router/releases/tag/react-router%406.24.0)
+- [`@remix-run/router@1.17.0`](https://github.com/remix-run/react-router/blob/main/packages/router/CHANGELOG.md#117)
+
+### Changes by Package
+
+- [`create-remix`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/create-remix/CHANGELOG.md#2100)
+- [`@remix-run/architect`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-architect/CHANGELOG.md#2100)
+- [`@remix-run/cloudflare`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-cloudflare/CHANGELOG.md#2100)
+- [`@remix-run/cloudflare-pages`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-cloudflare-pages/CHANGELOG.md#2100)
+- [`@remix-run/cloudflare-workers`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-cloudflare-workers/CHANGELOG.md#2100)
+- [`@remix-run/css-bundle`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-css-bundle/CHANGELOG.md#2100)
+- [`@remix-run/deno`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-deno/CHANGELOG.md#2100)
+- [`@remix-run/dev`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-dev/CHANGELOG.md#2100)
+- [`@remix-run/eslint-config`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-eslint-config/CHANGELOG.md#2100)
+- [`@remix-run/express`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-express/CHANGELOG.md#2100)
+- [`@remix-run/node`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-node/CHANGELOG.md#2100)
+- [`@remix-run/react`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-react/CHANGELOG.md#2100)
+- [`@remix-run/serve`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-serve/CHANGELOG.md#2100)
+- [`@remix-run/server-runtime`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-server-runtime/CHANGELOG.md#2100)
+- [`@remix-run/testing`](https://github.com/remix-run/remix/blob/remix%402.10.0/packages/remix-testing/CHANGELOG.md#2100)
+
+**Full Changelog**: [`v2.9.2...v2.10.0`](https://github.com/remix-run/remix/compare/remix@2.9.2...remix@2.10.0)
+
## v2.9.2
Date: 2024-05-10
diff --git a/contributors.yml b/contributors.yml
index 498dabb2672..2e0c69b5419 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -140,6 +140,7 @@
- depsimon
- derekr
- derenge
+- devbytyler
- devcamke
- developit
- devongovett
@@ -216,6 +217,7 @@
- gon250
- goncy
- gonzoscript
+- gotgenes
- GOWxx
- graham42
- GregBrimble
@@ -271,6 +273,7 @@
- jacobwgillespie
- jakeginnivan
- jakewtaylor
+- james0r
- jamiebuilds
- janhoogeveen
- Jannis-Morgenstern
@@ -349,18 +352,21 @@
- kiliman
- kimdontdoit
- KingSora
+- kisaragi-hiu
- kishanhitk
- kiyadotdev
- klauspaiva
- knowler
- konradkalemba
- krolebord
+- krystof-k
- ksjogo
- kubaprzetakiewicz
- kuldar
- kumard3
- lachlanjc
- larister
+- lasseklovstad
- laughnan
- lawrencecchen
- ledniy
@@ -441,6 +447,7 @@
- michaelgmcd
- michaelhelvey
- michaseel
+- michenly
- mikechabot
- mikeybinnswebdesign
- mirzafaizan
@@ -462,6 +469,7 @@
- mtt87
- muhammadjalabi
- mush159
+- mustak
- n8agrin
- na2hiro
- nareshbhatia
@@ -677,3 +685,4 @@
- zainfathoni
- zayenz
- zhe
+- zwhitchcox
diff --git a/docs/discussion/pending-ui.md b/docs/discussion/pending-ui.md
index 2840e25f074..a75d04428fe 100644
--- a/docs/discussion/pending-ui.md
+++ b/docs/discussion/pending-ui.md
@@ -101,7 +101,7 @@ export function ProjectList({ projects }) {
}
```
-While localized indicators on links are nice, they are incomplete. There are many other ways a navigation can be triggered: form submissions, back and forward button clicks in the browser chrome, action redirects, and imperative `navigate(path)` calls, so you'll typically want a global indicator to capture everything.
+While localized indicators on links are nice, they are incomplete. There are many other ways a navigation can be triggered: form submissions, back and forward button clicks in the browser, action redirects, and imperative `navigate(path)` calls, so you'll typically want a global indicator to capture everything.
### Record Creation
@@ -176,7 +176,7 @@ function ProjectListItem({ project }) {
const fetcher = useFetcher();
const starred = fetcher.formData
- ? // use to optimistic value if submitting
+ ? // use optimistic value if submitting
fetcher.formData.get("starred") === "1"
: // fall back to the database state
project.starred;
@@ -191,7 +191,7 @@ function ProjectListItem({ project }) {
// use optimistic value to allow interruptions
value={starred ? "0" : "1"}
>
- {/* display optimistic value */}
+ {/* 👇 display optimistic value */}
{starred ? "☆" : "★"}
@@ -204,7 +204,7 @@ function ProjectListItem({ project }) {
**Skeleton Fallback**: When data is deferred, you can add fallbacks with [``][suspense_component]. This allows the UI to render without waiting for the data to load, speeding up the perceived and actual performance of the application.
-```tsx lines=[11-14,23-27]
+```tsx lines=[11-14,24-28]
import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { defer } from "@remix-run/node"; // or cloudflare/deno
import { Await } from "@remix-run/react";
diff --git a/docs/file-conventions/root.md b/docs/file-conventions/root.md
index 16b2de4f8eb..29d4fe3a003 100644
--- a/docs/file-conventions/root.md
+++ b/docs/file-conventions/root.md
@@ -150,7 +150,9 @@ export function ErrorBoundary() {
`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 `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 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({
diff --git a/docs/file-conventions/vite-config.md b/docs/file-conventions/vite-config.md
index aa9900d2a4d..67a47c85dd7 100644
--- a/docs/file-conventions/vite-config.md
+++ b/docs/file-conventions/vite-config.md
@@ -52,7 +52,7 @@ The path to the `app` directory, relative to the project root. Defaults to
#### future
-The `future` config lets you opt-into future breaking changes via [Future Flags][future-flags]. Please see the [Current Future Flags][current-future-flags] section for a list of all available Future Flags.
+The `future` config lets you opt-into future breaking changes via [Future Flags][future-flags].
#### ignoredRouteFiles
@@ -143,4 +143,3 @@ You may also want to enable the `manifest` option since, when server bundles are
[rr-basename]: https://reactrouter.com/routers/create-browser-router#basename
[vite-public-base-path]: https://vitejs.dev/config/shared-options.html#base
[vite-base]: https://vitejs.dev/config/shared-options.html#base
-[current-future-flags]: ../start/future-flags#current-future-flags
diff --git a/docs/guides/api-development-strategy.md b/docs/guides/api-development-strategy.md
new file mode 100644
index 00000000000..75f6f93905d
--- /dev/null
+++ b/docs/guides/api-development-strategy.md
@@ -0,0 +1,68 @@
+---
+title: Development Strategy
+---
+
+# Gradual Feature Adoption with Future Flags
+
+In our approach to software development, we aim to achieve the following goals for major releases:
+
+1. **Incremental Feature Adoption:** Developers have the flexibility to choose and integrate new features and changes one by one, as they become available in the current major version. This is a departure from the traditional method of bundling all changes into a single new major release.
+2. **Seamless Version Upgrades:** By selectively incorporating new features ahead of time, developers can smoothly transition to new major versions without the need to modify their existing application code.
+
+## Unstable APIs and Future Flags
+
+We introduce new features into the current release with a future flag that looks something like `unstable_someFeature`. You can specify these flags in the Remix Vite Plugin `future` option in your [`vite.config.ts`][vite-config-future] file:
+
+```ts filename=vite.config.ts lines=[7-9]
+import { vitePlugin as remix } from "@remix-run/dev";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [
+ remix({
+ future: {
+ unstable_someFeature: true,
+ },
+ }),
+ ],
+});
+```
+
+If you are not yet using Vite, you can provide Future Flags via the [`remix.config.js` `future`][remix-config-future] option
+
+- Once an unstable feature reaches a stable state, we remove the special prefix and include the feature in the next minor release. At this point, the API's structure remains consistent throughout subsequent minor releases.
+
+- This approach allows us to refine the API collaboratively with early adopters, incorporating necessary changes in the unstable phase without affecting all users. The stable releases then benefit from these improvements without disruptions.
+
+- If you're utilizing features labeled with `unstable_*` flags, it's crucial to review the release notes for each minor release. This is because the behavior or structure of these features might evolve. Your feedback during this phase is invaluable in enhancing the feature before the final release!
+
+## Managing Breaking Changes with Future Flags
+
+When we introduce breaking changes, we do so within the context of the current major version, and we hide them behind future flags. For instance, if we're in `v2`, a breaking change might be placed under a future flag named `v3_somethingDifferent`.
+
+```ts filename=vite.config.ts lines=[7-9]
+import { vitePlugin as remix } from "@remix-run/dev";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [
+ remix({
+ future: {
+ v3_someFeature: true,
+ },
+ }),
+ ],
+});
+```
+
+- Both the existing `v2` behavior and the new `v3_somethingDifferent` behavior coexist simultaneously.
+- Applications can adopt changes incrementally, one step at a time, instead of having to adjust to a multitude of changes all at once in the next major release.
+- If all the `v3_*` future flags are enabled, transitioning to `v3` should ideally not necessitate any changes to your codebase.
+- Some future flags that bring about breaking changes initially start as `unstable_*` flags. These might undergo modifications during minor releases. Once they become `v3_*` future flags, the corresponding API is set and won't change further.
+
+## Summary
+
+Our development strategy focuses on gradual feature adoption and seamless version upgrades for major releases. This empowers developers to selectively integrate new features, avoiding the need for extensive code adjustments during version transitions. By introducing features through `unstable_*` flags, we refine the API collaboratively with early adopters while ensuring stable releases benefit from enhancements. Through careful management of breaking changes using `v3_*` flags, we provide the flexibility to adopt changes incrementally, facilitating a smoother transition between major versions. While this increases the complexity for developing Remix the framework, this developer-centric approach greatly simplifies application development with Remix, ultimately leading to improved software quality and (hopefully!) developer satisfaction.
+
+[vite-config-future]: ../file-conventions/vite-config#future
+[remix-config-future]: ../file-conventions/remix-config#future
diff --git a/docs/guides/fog-of-war.md b/docs/guides/fog-of-war.md
index cbddd90c8d9..f2b7481cd84 100644
--- a/docs/guides/fog-of-war.md
+++ b/docs/guides/fog-of-war.md
@@ -4,25 +4,29 @@ title: Fog of War
# Fog Of War
-Remix introduced support for "Fog of War" ([RFC][rfc]) behind the [`future.unstable_fogOfWar`][future-flags] flag in [`v2.10.0`][2.10.0] which allows you to opt-into this behavior which will become the default in Remix v3.
+This is an unstable API and will continue to change, do not adopt in production
-## Overview
+Remix introduced support for "Fog of War" ([RFC][rfc]) behind the `future.unstable_fogOfWar` [Future Flag][future-flags] in [`v2.10.0`][2.10.0]. This allows you to opt-into this behavior which will become the default in the next major version of Remix - a.k.a. React Router v7 ([1][rr-v7], [2][rr-v7-2]).
-When you enable Fog of War, Remix will no longer send a full route manifest on initial load. Previously this would be loaded through a JS file on initial load (i.e., `/assets/manifest-[hash]].js`). The manifest does not contain the route module implementations, but rather just their paths and some meta information on their imports and whether they have a server side `loader`/`action`, etc. Having this full manifest up-front allows Remix to do synchronous client-side route matching on Link clicks and kick off the loads for route modules and data imediately. For small-to-medium-sized apps, loading the full manifest up-front is usually not prohibitive as it is highly cacheable and gzips down quite well. However, at scale we found that this manifest could grow large enough to impact some performance metrics.
+## Current Behavior
-The "Fog of War" approach solves this problem by only sending up the initially-rendered routes in the initial manifest, and then loads additional routes as needed as the user navigates around the application and patches them into the manifest. Over time, the manifest grows to include only portions of the app the user navigated to.
+Currently, Remix loads the complete route manifest in a JS file on initial load (i.e., `/assets/manifest-[hash].js`). The manifest does not contain the route module implementations, but rather their URL paths and meta information (route JS/CSS imports, whether they have a `loader`/`action` on the server, etc.). Having this full manifest up-front allows Remix to do synchronous client-side route matching on Link clicks and kick off the loads for route modules and data immediately. For small-to-medium-sized apps, loading the full manifest up-front is usually not prohibitive as it is highly cacheable and gzips down quite well. However, at scale we found that this manifest could grow large enough to impact some performance metrics.
+
+## New Behavior
+
+When you enable "Fog of War", Remix will no longer send a full route manifest on initial load. Instead, your SSR render will only include the SSR routes in the initial manifest and additional routes will be loaded as the user navigates around the application. Over time, the manifest grows to include the portions of the app the user navigated to.
### Eager Route Discovery
-There is a tradeoff with this type of lazy-route discovery though in that Remix can no longer perform synchronous route matching on link clicks, which can lead to waterfalls.
+As always, there is a tradeoff with this type of lazy-route discovery. It improves initial application load times -- but Remix can no longer perform synchronous route matching on link clicks, which can lead to waterfalls.
-Without prefetching in the current architecture, clicking a link would look something like this:
+In the current architecture (without using ``), clicking a link would look something like this:
```
click /a
|-- load route module -->
|-- load route data -->
- render /a
+ | render /a
```
In the Fog of War architecture, clicking a link can introduce a waterfall:
@@ -32,24 +36,24 @@ click /a
|-- discover route -->
|-- load route module -->
|-- load route data -->
- render /a
+ | render /a
```
-As we all know, Remix hates waterfalls, so the Fog of War feature implements an optimization to avoid them in the vast majority of cases. By default, all [``][link] and [``][navlink] components rendered on the page will be batched up and eagerly "discovered" via a request to the server. This request will match all current link paths on the server and send back all required route manifest entries. Under the vast majority of cases, this request will complete prior to the user clicking any links (since users don't usually click links in the first few hundred milliseconds) and the manifest will be patched before any links are clicked. Then, when a link is clicked, Remix is able to do synchronous client-side matching as if the Fog of War behavior wasn't even present.
+As we all know, Remix hates waterfalls, so the Fog of War feature implements an optimization to avoid them in the majority of cases. By default, all [``][link] and [``][navlink] components rendered on the page will be batched up and eagerly "discovered" via a request to the server. This request will match all current link paths on the server and send back all required route manifest entries. Under the majority of cases, this request should complete prior to the user clicking any links (since users don't usually click links in the first few hundred milliseconds) and the manifest will be patched before any links are clicked. Then, when a link is clicked, Remix is able to do synchronous client-side matching as if the Fog of War behavior wasn't even present.
If you wish to opt-out of this eager route discovery on a per-link basis, you can do that via the [`discover="none"`][link-discover] prop (the default value is `discover="render"`).
### Notable Changes
- When this feature is enabled, the route manifest in `window.__remixManifest.routes` will only contain the minimal required routes on initial SSR, and routes will be added to it dynamically as the user navigates around
-- The Remix handler now has a new internal `__manifest` endpoint through which it will fetch manifest patches
+- The Remix handler now has a new internal `/__manifest` endpoint through which it will fetch manifest patches
- ⚠️ This is considered an internal implementation detail and is not intended to be requested by application code.
-## Details
-
[rfc]: https://github.com/remix-run/react-router/discussions/11113
-[future-flags]: ../file-conventions/remix-config#future
+[future-flags]: ../guides/api-development-strategy
[2.10.0]: https://github.com/remix-run/remix/blob/main/CHANGELOG.md#v2100
[link]: ../components/link
[navlink]: ../components/nav-link
[link-discover]: ../components/link#discover
+[rr-v7]: https://remix.run/blog/merging-remix-and-react-router
+[rr-v7-2]: https://remix.run/blog/incremental-path-to-react-19
diff --git a/docs/guides/single-fetch.md b/docs/guides/single-fetch.md
index aac9e81b3ad..09bdd52e854 100644
--- a/docs/guides/single-fetch.md
+++ b/docs/guides/single-fetch.md
@@ -4,43 +4,64 @@ title: Single Fetch
# Single Fetch
-Remix introduced support for "Single Fetch" ([RFC][rfc]) behind the [`future.unstable_singleFetch`][future-flags] flag in [`v2.9.0`][2.9.0] which allows you to opt-into this behavior which will become the default in Remix v3.
+This is an unstable API and will continue to change, do not adopt in production
+
+Single fetch is a new data data loading strategy and streaming format. When you enable Single Fetch, Remix will make a single HTTP call to your server on client-side transitions, instead of multiple HTTP calls in parallel (one per loader). Additionally, Single Fetch also allows you to send down naked objects from your `loader` and `action`, such as `Date`, `Error`, `Promise`, `RegExp`, and more.
## Overview
-When you enable Single Fetch, Remix will make a single HTTP call to your server on client-side transitions, instead of multiple HTTP calls in parallel (one per loader). If you are currently returning `Response` instances from your loaders (i.e., `json`/`defer`) then you shouldn't _need_ to make many changes to your app code, but please read through the "breaking" changes below to be aware of some of the underlying behavior changes - specifically around serialization and status/header behavior.
+Remix introduced support for "Single Fetch" ([RFC][rfc]) behind the [`future.unstable_singleFetch`][future-flags] flag in [`v2.9.0`][2.9.0] which allows you to opt-into this behavior. Single Fetch will be the default in [React Router v7][merging-remix-and-rr].
-### Breaking Changes
+Enabling Single Fetch is intended to be low-effort up-front, and then allow you to adopt all breaking changes iteratively over time. You can start by applying the minimal required changes to [enable Single Fetch][start], then use the [migration guide][migration-guide] to make incremental changes in your application to ensure a smooth, non-breaking upgrade to [React Router v7][merging-remix-and-rr].
-- Single fetch uses a new streaming format under the hood via [`turbo-stream`][turbo-stream], which means that we can stream down more complex data than just JSON
-- Naked objects returned from `loader` and `action` functions are no longer automatically converted into a JSON `Response` and are serialized as-is over the wire
-- To get the most accurate type inference, you should do two things:
- - Add `@remix-run/react/future/single-fetch.d.ts` to the end of your `tsconfig.json`'s `compilerOptions.types` array
- - Begin using `unstable_defineLoader`/`unstable_defineAction` in your routes
- - This can be done incrementally - you should have _mostly_ accurate type inference in your current state
-- Revalidation after an `action` `4xx`/`5xx` `Response` is now opt-in, versus opt-out
-- The [`headers`][headers] function is no longer used when Single Fetch is enabled, in favor of the new `response` stub passed to your `loader`/`action` functions
-- The old `installGlobals()` polyfill doesn't work for Single Fetch, you must either use the native Node 20 `fetch` API or call `installGlobals({ nativeFetch: true })` in your custom server to get the [undici-based polyfill][undici-polyfill]
+Please also review the [Breaking Changes][breaking-changes] so you can be aware of some of the underlying behavior changes, specifically around serialization and status/header behavior.
-## Details
+## Enabling Single Fetch
-### Streaming Data Format
+**1. Enable the future flag**
-Previously, Remix used `JSON.stringify` to serialize your loader/action data over the wire, and needed to implement a custom streaming format to support `defer` responses.
+```ts filename=vite.config.ts lines=[6]
+export default defineConfig({
+ plugins: [
+ remix({
+ future: {
+ // ...
+ unstable_singleFetch: true,
+ },
+ }),
+ // ...
+ ],
+});
+```
-With Single Fetch, Remix now uses [`turbo-stream`][turbo-stream] under the hood which provides first class support for streaming and allows you to automatically serialize/deserialize more complex data than JSON. The following data types can be streamed down directly via `turbo-stream`: `BigInt`, `Date`, `Error`, `Map`, `Promise`, `RegExp`, `Set`, `Symbol`, and `URL`. Subtypes of `Error` are also supported as long as they have a globally available constructor on the client (`SyntaxError`, `TypeError`, etc.).
+**2. Deprecated `fetch` polyfill**
-This may or may not require any immediate changes to your code once enabling Single Fetch:
+Single Fetch requires using [`undici`][undici] as your `fetch` polyfill, or using the built-in `fetch` on Node 20+, because it relies on APIs available there that are not in the `@remix-run/web-fetch` polyfill. Please refer to the [Undici][undici-polyfill] section in the 2.9.0 release notes below for more details.
-- ✅ `json` responses returned from `loader`/`action` functions will still be serialized via `JSON.stringify` so if you return a `Date`, you'll receive a `string` from `useLoaderData`/`useActionData`
-- ⚠️ If you're returning a `defer` instance or a naked object, it will now be serialized via `turbo-stream`, so if you return a `Date`, you'll receive a `Date` from `useLoaderData`/`useActionData`
- - If you wish to maintain current behavior (excluding streaming `defer` responses), you may just wrap any existing naked object returns in `json`
+- If you are using Node 20+, remove any calls to `installGlobals()` and use Node's built-in `fetch` (this is the same thing as `undici`).
-This also means that you no longer need to use the `defer` utility to send `Promise` instances over the wire! You can include a `Promise` anywhere in a naked object and pick it up on `useLoaderData().whatever`. You can also nest `Promise`'s if needed - but beware of potential UX implications.
+- If you are managing your own server and calling `installGlobals()`, you will need to call `installGlobals({ nativeFetch: true })` to use `undici`.
-Once adopting Single Fetch, it is recommended that you incrementally remove the usage of `json`/`defer` throughout your application in favor of returning raw objects.
+ ```diff
+ - installGlobals();
+ + installGlobals({ nativeFetch: true });
+ ```
+
+- If you are using `remix-serve`, it will use `undici` automatically if Single Fetch is enabled.
+
+- If you are using miniflare/cloudflare worker with your remix project, ensure your [compatibility flag][compatibility-flag] is set to `2023-03-01` or later as well.
+
+**3. Remove document-level `headers` implementation (if you have one)**
-### React Rendering APIs
+The [`headers`][headers] export is not longer used when single fetch is enabled. In many cases you may have been just re-returning the headers from your loader `Response` instances to apply them to document requests, and if so, you may can likely just remove the export and those Repsonse headers will apply to document requests automatically. If you were doing more complex logic for document headers in the `headers` function, then you will need to migrate those to the new [Response Stub][responsestub] instance in your `loader` functions.
+
+**4. Add `nonce` to `` (if you are using a CSP)**
+
+The `` component renders inline scripts that handle the streaming data on the client side. If you have a [content security policy for scripts][csp] with [nonce-sources][csp-nonce], you can use `` to pass through the nonce to these `