diff --git a/.changeset/mighty-grapes-laugh.md b/.changeset/mighty-grapes-laugh.md
new file mode 100644
index 00000000000..2ed960e791b
--- /dev/null
+++ b/.changeset/mighty-grapes-laugh.md
@@ -0,0 +1,6 @@
+---
+"@remix-run/server-runtime": patch
+---
+
+- Ensure `loader` `request`'s proxy headers on document `action` submissions
+- Fix an issue where loader `request`'s reflected `method: "POST"` on document submissions
diff --git a/.changeset/sharp-paws-nail.md b/.changeset/sharp-paws-nail.md
new file mode 100644
index 00000000000..66ec7a06229
--- /dev/null
+++ b/.changeset/sharp-paws-nail.md
@@ -0,0 +1,5 @@
+---
+"@remix-run/server-runtime": patch
+---
+
+Fix error boundary tracking for multiple errors bubbling to the same boundary
diff --git a/integration/catch-boundary-test.ts b/integration/catch-boundary-test.ts
index c31d61bfcde..37ccb0a680c 100644
--- a/integration/catch-boundary-test.ts
+++ b/integration/catch-boundary-test.ts
@@ -67,6 +67,9 @@ test.describe("CatchBoundary", () => {
${HAS_BOUNDARY_LOADER}
+
+ ${HAS_BOUNDARY_LOADER}/child
+
${NO_BOUNDARY_LOADER}
@@ -111,11 +114,27 @@ test.describe("CatchBoundary", () => {
`,
[`app/routes${HAS_BOUNDARY_LOADER}.jsx`]: js`
+ import { useCatch } from '@remix-run/react';
export function loader() {
throw new Response("", { status: 401 })
}
export function CatchBoundary() {
- return
${OWN_BOUNDARY_TEXT}
+ let caught = useCatch();
+ return (
+ <>
+ ${OWN_BOUNDARY_TEXT}
+ {caught.status}
+ >
+ );
+ }
+ export default function Index() {
+ return
+ }
+ `,
+
+ [`app/routes${HAS_BOUNDARY_LOADER}/child.jsx`]: js`
+ export function loader() {
+ throw new Response("", { status: 404 })
}
export default function Index() {
return
@@ -292,4 +311,23 @@ test.describe("CatchBoundary", () => {
expect(await app.getHtml("#child-catch")).toMatch("400");
expect(await app.getHtml("#child-catch")).toMatch("Caught!");
});
+
+ test("prefers parent catch when child loader also bubbles, document request", async () => {
+ let res = await fixture.requestDocument(`${HAS_BOUNDARY_LOADER}/child`);
+ expect(res.status).toBe(401);
+ let text = await res.text();
+ expect(text).toMatch(OWN_BOUNDARY_TEXT);
+ expect(text).toMatch('401
');
+ });
+
+ test("prefers parent catch when child loader also bubbles, client transition", async ({
+ page,
+ }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/");
+ await app.clickLink(`${HAS_BOUNDARY_LOADER}/child`);
+ await page.waitForSelector("#boundary-loader");
+ expect(await app.getHtml("#boundary-loader")).toMatch(OWN_BOUNDARY_TEXT);
+ expect(await app.getHtml("#status")).toMatch("401");
+ });
});
diff --git a/integration/request-test.ts b/integration/request-test.ts
new file mode 100644
index 00000000000..db8c47ec5a7
--- /dev/null
+++ b/integration/request-test.ts
@@ -0,0 +1,162 @@
+import { test, expect } from "@playwright/test";
+
+import { PlaywrightFixture } from "./helpers/playwright-fixture";
+import type { Fixture, AppFixture } from "./helpers/create-fixture";
+import { createAppFixture, createFixture, js } from "./helpers/create-fixture";
+
+let fixture: Fixture;
+let appFixture: AppFixture;
+
+test.beforeAll(async () => {
+ fixture = await createFixture({
+ files: {
+ "app/routes/index.jsx": js`
+ import { json } from "@remix-run/node";
+ import { Form, useLoaderData, useActionData } from "@remix-run/react";
+ export async function loader({ request }) {
+ let text = (await request.text());
+ return json({
+ method: request.method,
+ url: request.url,
+ headers: Object.fromEntries(request.headers.entries()),
+ text,
+ });
+ }
+ export async function action({ request }) {
+ let text = (await request.text());
+ return json({
+ method: request.method,
+ url: request.url,
+ headers: Object.fromEntries(request.headers.entries()),
+ text,
+ });
+ }
+ export default function Index() {
+ let loaderData = useLoaderData();
+ let actionData = useActionData();
+ return (
+
+
+
+
+
+
+
{JSON.stringify(loaderData)}
+ {actionData ?
+
{JSON.stringify(actionData)}
:
+ null}
+
+ )
+ }
+ `,
+ },
+ });
+
+ appFixture = await createAppFixture(fixture);
+});
+
+test.afterAll(() => appFixture.close());
+
+test("loader request on SSR GET requests", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/");
+ await app.clickElement("#set-cookie");
+
+ let loaderData = JSON.parse(await page.locator("#loader-data").innerHTML());
+ expect(loaderData.method).toEqual("GET");
+ expect(loaderData.url).toMatch(/^http:\/\/localhost:\d+\/$/);
+ expect(loaderData.headers.cookie).toEqual(undefined);
+ expect(loaderData.text).toEqual("");
+
+ await app.clickElement("#submit-get-ssr");
+
+ loaderData = JSON.parse(await page.locator("#loader-data").innerHTML());
+ expect(loaderData.method).toEqual("GET");
+ expect(loaderData.url).toMatch(/^http:\/\/localhost:\d+\/\?type=ssr$/);
+ expect(loaderData.headers.cookie).toEqual("cookie=nomnom");
+ expect(loaderData.text).toEqual("");
+});
+
+test("loader request on CSR GET requests", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/");
+ await app.clickElement("#set-cookie");
+
+ let loaderData = JSON.parse(await page.locator("#loader-data").innerHTML());
+ expect(loaderData.method).toEqual("GET");
+ expect(loaderData.url).toMatch(/^http:\/\/localhost:\d+\/$/);
+ expect(loaderData.headers.cookie).toEqual(undefined);
+ expect(loaderData.text).toEqual("");
+
+ await app.clickElement("#submit-get-csr");
+
+ loaderData = JSON.parse(await page.locator("#loader-data").innerHTML());
+ expect(loaderData.method).toEqual("GET");
+ expect(loaderData.url).toMatch(/^http:\/\/localhost:\d+\/\?type=csr$/);
+ expect(loaderData.headers.cookie).toEqual("cookie=nomnom");
+ expect(loaderData.text).toEqual("");
+});
+
+test("action + loader requests SSR POST requests", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/");
+ await app.clickElement("#set-cookie");
+
+ let loaderData = JSON.parse(await page.locator("#loader-data").innerHTML());
+ expect(loaderData.method).toEqual("GET");
+ expect(loaderData.url).toMatch(/^http:\/\/localhost:\d+\/$/);
+ expect(loaderData.headers.cookie).toEqual(undefined);
+ expect(loaderData.text).toEqual("");
+
+ await app.clickElement("#submit-post-ssr");
+
+ let actionData = JSON.parse(await page.locator("#action-data").innerHTML());
+ expect(actionData.method).toEqual("POST");
+ expect(actionData.url).toMatch(/^http:\/\/localhost:\d+\/$/);
+ expect(actionData.headers.cookie).toEqual("cookie=nomnom");
+ expect(actionData.text).toEqual("type=ssr");
+
+ loaderData = JSON.parse(await page.locator("#loader-data").innerHTML());
+ expect(loaderData.method).toEqual("GET");
+ expect(loaderData.url).toMatch(/^http:\/\/localhost:\d+\/$/);
+ expect(loaderData.headers.cookie).toEqual("cookie=nomnom");
+ expect(loaderData.text).toEqual("");
+});
+
+test("action + loader requests on CSR POST requests", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/");
+ await app.clickElement("#set-cookie");
+
+ let loaderData = JSON.parse(await page.locator("#loader-data").innerHTML());
+ expect(loaderData.method).toEqual("GET");
+ expect(loaderData.url).toMatch(/^http:\/\/localhost:\d+\/$/);
+ expect(loaderData.headers.cookie).toEqual(undefined);
+ expect(loaderData.text).toEqual("");
+
+ await app.clickElement("#submit-post-csr");
+
+ let actionData = JSON.parse(await page.locator("#action-data").innerHTML());
+ expect(actionData.method).toEqual("POST");
+ expect(actionData.url).toMatch(/^http:\/\/localhost:\d+\/$/);
+ expect(actionData.headers.cookie).toEqual("cookie=nomnom");
+ expect(actionData.text).toEqual("type=csr");
+
+ loaderData = JSON.parse(await page.locator("#loader-data").innerHTML());
+ expect(loaderData.method).toEqual("GET");
+ expect(loaderData.url).toMatch(/^http:\/\/localhost:\d+\/$/);
+ expect(loaderData.headers.cookie).toEqual("cookie=nomnom");
+ expect(loaderData.text).toEqual("");
+});
diff --git a/packages/remix-react/package.json b/packages/remix-react/package.json
index 815d0c448e8..5332bd0a8d5 100644
--- a/packages/remix-react/package.json
+++ b/packages/remix-react/package.json
@@ -16,8 +16,8 @@
"typings": "dist/index.d.ts",
"module": "dist/esm/index.js",
"dependencies": {
- "@remix-run/router": "1.0.5",
- "react-router-dom": "6.4.5"
+ "@remix-run/router": "1.1.0-pre.0",
+ "react-router-dom": "6.5.0-pre.0"
},
"devDependencies": {
"@remix-run/server-runtime": "1.8.2",
diff --git a/packages/remix-server-runtime/package.json b/packages/remix-server-runtime/package.json
index 65601f6b0f2..dfc12276571 100644
--- a/packages/remix-server-runtime/package.json
+++ b/packages/remix-server-runtime/package.json
@@ -16,7 +16,7 @@
"typings": "dist/index.d.ts",
"module": "dist/esm/index.js",
"dependencies": {
- "@remix-run/router": "1.0.5",
+ "@remix-run/router": "1.1.0-pre.0",
"@types/cookie": "^0.4.0",
"@web3-storage/multipart-parser": "^1.0.0",
"cookie": "^0.4.1",
diff --git a/yarn.lock b/yarn.lock
index 0bc88e5bac6..bcd64daba9e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2174,6 +2174,11 @@
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.0.5.tgz#d5c65626add4c3c185a89aa5bd38b1e42daec075"
integrity sha512-my0Mycd+jruq/1lQuO5LBB6WTlL/e8DTCYWp44DfMTDcXz8DcTlgF0ISaLsGewt+ctHN+yA8xMq3q/N7uWJPug==
+"@remix-run/router@1.1.0-pre.0":
+ version "1.1.0-pre.0"
+ resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.1.0-pre.0.tgz#c33db6bd13c6d3847f73f618008ebecdaec237c7"
+ integrity sha512-FFa1aGAYUZm+PdOO2a7r/4gxLqSIOq1Qav3g6wwRgWj28PNyO7CjdUNER4O4DSKVVyob21mhZHvTe25z+2xnZw==
+
"@remix-run/web-blob@^3.0.3", "@remix-run/web-blob@^3.0.4":
version "3.0.4"
resolved "https://registry.npmjs.org/@remix-run/web-blob/-/web-blob-3.0.4.tgz"
@@ -10722,20 +10727,20 @@ react-is@^17.0.1:
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-react-router-dom@6.4.5:
- version "6.4.5"
- resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.5.tgz#4fdb12efef4f3848c693a76afbeaed1f6ca02047"
- integrity sha512-a7HsgikBR0wNfroBHcZUCd9+mLRqZS8R5U1Z1mzLWxFXEkUT3vR1XXmSIVoVpxVX8Bar0nQYYYc9Yipq8dWwAA==
+react-router-dom@6.5.0-pre.0:
+ version "6.5.0-pre.0"
+ resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.5.0-pre.0.tgz#73dc3861f43b1bc83a854bd5d3d3ab4d7beb1710"
+ integrity sha512-Hq8lhsAqLH4LDewj6OeDCiEel5RP6El1DsxohxwgOctt+xq4Q6kGiiuVnLA+eHl2wXFCEF8ZSK/kOddGKfLWYA==
dependencies:
- "@remix-run/router" "1.0.5"
- react-router "6.4.5"
+ "@remix-run/router" "1.1.0-pre.0"
+ react-router "6.5.0-pre.0"
-react-router@6.4.5:
- version "6.4.5"
- resolved "https://registry.npmjs.org/react-router/-/react-router-6.4.5.tgz#73f382af2c8b9a86d74e761a7c5fc3ce7cb0024d"
- integrity sha512-1RQJ8bM70YEumHIlNUYc6mFfUDoWa5EgPDenK/fq0bxD8DYpQUi/S6Zoft+9DBrh2xmtg92N5HMAJgGWDhKJ5Q==
+react-router@6.5.0-pre.0:
+ version "6.5.0-pre.0"
+ resolved "https://registry.npmjs.org/react-router/-/react-router-6.5.0-pre.0.tgz#513b2fe858736443082a33a7a9d2f728b2bd7fe4"
+ integrity sha512-JqRGYSGzySjcqVMkgCa+1i3zvXiH2IHUfEPac71sJn5ArH5tvR5GmraWUn+D5t7Yzq2dz11is4u2SzHiT0pGdg==
dependencies:
- "@remix-run/router" "1.0.5"
+ "@remix-run/router" "1.1.0-pre.0"
react@^18.2.0:
version "18.2.0"