diff --git a/.changeset/chilled-masks-search.md b/.changeset/chilled-masks-search.md
new file mode 100644
index 0000000000..2dfd04cf76
--- /dev/null
+++ b/.changeset/chilled-masks-search.md
@@ -0,0 +1,12 @@
+---
+"react-router-dom": major
+"react-router": major
+---
+
+Remove the original `defer` implementation in favor of using raw promises via single fetch and `turbo-stream`. This removes these exports from React Router:
+
+- `defer`
+- `AbortedDeferredError`
+- `type TypedDeferredData`
+- `UNSAFE_DeferredData`
+- `UNSAFE_DEFERRED_SYMBOL`,
diff --git a/integration/client-data-test.ts b/integration/client-data-test.ts
index fd0fd9fd6f..41b6556849 100644
--- a/integration/client-data-test.ts
+++ b/integration/client-data-test.ts
@@ -318,13 +318,13 @@ test.describe("Client Data", () => {
}),
"app/routes/parent.child.tsx": js`
import * as React from 'react';
- import { defer, json } from "react-router"
+ import { json } from "react-router"
import { Await, useLoaderData } from "react-router"
export function loader() {
- return defer({
+ return {
message: 'Child Server Loader',
lazy: new Promise(r => setTimeout(() => r("Child Deferred Data"), 1000)),
- });
+ };
}
export async function clientLoader({ serverLoader }) {
let data = await serverLoader();
diff --git a/integration/defer-loader-test.ts b/integration/defer-loader-test.ts
index d5858b0481..e8464e2105 100644
--- a/integration/defer-loader-test.ts
+++ b/integration/defer-loader-test.ts
@@ -28,23 +28,23 @@ test.describe("deferred loaders", () => {
`,
"app/routes/redirect.tsx": js`
- import { defer } from "react-router";
- export function loader() {
- return defer({food: "pizza"}, { status: 301, headers: { Location: "/?redirected" } });
+ export function loader({ response }) {
+ response.status = 301;
+ response.headers.set("Location", "/?redirected");
+ return { food: "pizza" };
}
export default function Redirect() {return null;}
`,
"app/routes/direct-promise-access.tsx": js`
import * as React from "react";
- import { defer } from "react-router";
import { useLoaderData, Link, Await } from "react-router";
export function loader() {
- return defer({
+ return {
bar: new Promise(async (resolve, reject) => {
resolve("hamburger");
}),
- });
+ };
}
let count = 0;
export default function Index() {
diff --git a/integration/defer-test.ts b/integration/defer-test.ts
index 9d16c38dff..4c8dfc792e 100644
--- a/integration/defer-test.ts
+++ b/integration/defer-test.ts
@@ -15,8 +15,6 @@ const DEFERRED_ID = "DEFERRED_ID";
const RESOLVED_DEFERRED_ID = "RESOLVED_DEFERRED_ID";
const FALLBACK_ID = "FALLBACK_ID";
const ERROR_ID = "ERROR_ID";
-const UNDEFINED_ERROR_ID = "UNDEFINED_ERROR_ID";
-const NEVER_SHOW_ID = "NEVER_SHOW_ID";
const ERROR_BOUNDARY_ID = "ERROR_BOUNDARY_ID";
const MANUAL_RESOLVED_ID = "MANUAL_RESOLVED_ID";
const MANUAL_FALLBACK_ID = "MANUAL_FALLBACK_ID";
@@ -75,7 +73,6 @@ test.describe("non-aborted", () => {
}
`,
"app/root.tsx": js`
- import { defer } from "react-router";
import { Links, Meta, Outlet, Scripts, useLoaderData } from "react-router";
import Counter from "~/components/counter";
import Interactive from "~/components/interactive";
@@ -84,7 +81,7 @@ test.describe("non-aborted", () => {
return [{ title: "New Remix App" }];
};
- export const loader = () => defer({
+ export const loader = () => ({
id: "${ROOT_ID}",
});
@@ -116,14 +113,13 @@ test.describe("non-aborted", () => {
`,
"app/routes/_index.tsx": js`
- import { defer } from "react-router";
import { Link, useLoaderData } from "react-router";
import Counter from "~/components/counter";
export function loader() {
- return defer({
+ return {
id: "${INDEX_ID}",
- });
+ };
}
export default function Index() {
@@ -148,15 +144,14 @@ test.describe("non-aborted", () => {
"app/routes/deferred-noscript-resolved.tsx": js`
import { Suspense } from "react";
- import { defer } from "react-router";
import { Await, Link, useLoaderData } from "react-router";
import Counter from "~/components/counter";
export function loader() {
- return defer({
+ return {
deferredId: "${DEFERRED_ID}",
resolvedId: Promise.resolve("${RESOLVED_DEFERRED_ID}"),
- });
+ };
}
export default function Deferred() {
@@ -183,19 +178,18 @@ test.describe("non-aborted", () => {
"app/routes/deferred-noscript-unresolved.tsx": js`
import { Suspense } from "react";
- import { defer } from "react-router";
import { Await, Link, useLoaderData } from "react-router";
import Counter from "~/components/counter";
export function loader() {
- return defer({
+ return {
deferredId: "${DEFERRED_ID}",
resolvedId: new Promise(
(resolve) => setTimeout(() => {
resolve("${RESOLVED_DEFERRED_ID}");
}, 10)
),
- });
+ };
}
export default function Deferred() {
@@ -222,16 +216,15 @@ test.describe("non-aborted", () => {
"app/routes/deferred-script-resolved.tsx": js`
import { Suspense } from "react";
- import { defer } from "react-router";
import { Await, Link, useLoaderData } from "react-router";
import Counter from "~/components/counter";
export function loader() {
- return defer({
+ return {
deferredId: "${DEFERRED_ID}",
resolvedId: Promise.resolve("${RESOLVED_DEFERRED_ID}"),
deferredUndefined: Promise.resolve(undefined),
- });
+ };
}
export default function Deferred() {
@@ -258,12 +251,11 @@ test.describe("non-aborted", () => {
"app/routes/deferred-script-unresolved.tsx": js`
import { Suspense } from "react";
- import { defer } from "react-router";
import { Await, Link, useLoaderData } from "react-router";
import Counter from "~/components/counter";
export function loader() {
- return defer({
+ return {
deferredId: "${DEFERRED_ID}",
resolvedId: new Promise(
(resolve) => setTimeout(() => {
@@ -275,7 +267,7 @@ test.describe("non-aborted", () => {
resolve(undefined);
}, 10)
),
- });
+ };
}
export default function Deferred() {
@@ -302,15 +294,14 @@ test.describe("non-aborted", () => {
"app/routes/deferred-script-rejected.tsx": js`
import { Suspense } from "react";
- import { defer } from "react-router";
import { Await, Link, useLoaderData } from "react-router";
import Counter from "~/components/counter";
export function loader() {
- return defer({
+ return {
deferredId: "${DEFERRED_ID}",
resolvedId: Promise.reject(new Error("${RESOLVED_DEFERRED_ID}")),
- });
+ };
}
export default function Deferred() {
@@ -343,24 +334,18 @@ test.describe("non-aborted", () => {
"app/routes/deferred-script-unrejected.tsx": js`
import { Suspense } from "react";
- import { defer } from "react-router";
import { Await, Link, useLoaderData } from "react-router";
import Counter from "~/components/counter";
export function loader() {
- return defer({
+ return {
deferredId: "${DEFERRED_ID}",
resolvedId: new Promise(
(_, reject) => setTimeout(() => {
reject(new Error("${RESOLVED_DEFERRED_ID}"));
}, 10)
),
- resolvedUndefined: new Promise(
- (resolve) => setTimeout(() => {
- resolve(undefined);
- }, 10)
- ),
- });
+ };
}
export default function Deferred() {
@@ -386,22 +371,6 @@ test.describe("non-aborted", () => {
)}
/>
-