diff --git a/.changeset/yellow-ears-begin.md b/.changeset/yellow-ears-begin.md
new file mode 100644
index 0000000000..8d0c3c80d8
--- /dev/null
+++ b/.changeset/yellow-ears-begin.md
@@ -0,0 +1,5 @@
+---
+"react-router": patch
+---
+
+Fail gracefully on manifest version mismatch logic if `sessionStorage` access is blocked
diff --git a/integration/session-storage-denied-test.ts b/integration/session-storage-denied-test.ts
new file mode 100644
index 0000000000..4c18b4e2f9
--- /dev/null
+++ b/integration/session-storage-denied-test.ts
@@ -0,0 +1,115 @@
+import { test, expect } from "@playwright/test";
+import {
+ createAppFixture,
+ createFixture,
+ js,
+ type AppFixture,
+ type Fixture,
+} from "./helpers/create-fixture.js";
+import { PlaywrightFixture } from "./helpers/playwright-fixture.js";
+
+test.describe("sessionStorage denied", () => {
+ let fixture: Fixture;
+ let appFixture: AppFixture;
+
+ test.beforeAll(async () => {
+ fixture = await createFixture({
+ files: {
+ "app/root.tsx": js`
+ import * as React from "react";
+ import { Link, Links, Meta, Outlet, Scripts, useRouteError } from "react-router";
+
+ export function ErrorBoundary() {
+ const error = useRouteError();
+ console.error("ErrorBoundary caught:", error);
+ return (
+
+
+ Error
+
+
+
+
+ Application Error
+ {error?.message || "Unknown error"}
+
+
+
+ );
+ }
+
+ export default function Root() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ `,
+ "app/routes/_index.tsx": js`
+ export default function Index() {
+ return Home
;
+ }
+ `,
+ "app/routes/docs.tsx": js`
+ export default function Docs() {
+ return Documentation
;
+ }
+ `,
+ },
+ });
+ appFixture = await createAppFixture(fixture);
+ });
+
+ test("should handle navigation gracefully when storage is blocked", async ({
+ page,
+ context,
+ }) => {
+ await context.addInitScript(() => {
+ const storageError = new DOMException(
+ "Failed to read the 'sessionStorage' property from 'Window': Access is denied for this document.",
+ "SecurityError",
+ );
+
+ ["sessionStorage", "localStorage"].forEach((storage) => {
+ Object.defineProperty(window, storage, {
+ get() {
+ throw storageError;
+ },
+ set() {
+ throw storageError;
+ },
+ configurable: false,
+ });
+ });
+ });
+
+ let app = new PlaywrightFixture(appFixture, page);
+
+ await app.goto("/");
+ await expect(page.locator("h1")).toContainText("Home");
+
+ await app.clickLink("/docs");
+ await expect(page).toHaveURL(/\/docs$/);
+ await expect(page.locator("h1")).toContainText("Documentation");
+
+ await page.goBack();
+ await expect(page).toHaveURL(/\/$/);
+ await expect(page.locator("h1")).toContainText("Home");
+
+ await page.goForward();
+ await expect(page).toHaveURL(/\/docs$/);
+ await expect(page.locator("h1")).toContainText("Documentation");
+ });
+});
diff --git a/packages/react-router/lib/dom/ssr/fog-of-war.ts b/packages/react-router/lib/dom/ssr/fog-of-war.ts
index 5770fda779..3bfc807517 100644
--- a/packages/react-router/lib/dom/ssr/fog-of-war.ts
+++ b/packages/react-router/lib/dom/ssr/fog-of-war.ts
@@ -263,21 +263,26 @@ export async function fetchAndApplyManifestPatches(
return;
}
- // This will hard reload the destination path on navigations, or the
- // current path on fetcher calls
- if (
- sessionStorage.getItem(MANIFEST_VERSION_STORAGE_KEY) ===
- manifest.version
- ) {
- // We've already tried fixing for this version, don' try again to
- // avoid loops - just let this navigation/fetch 404
- console.error(
- "Unable to discover routes due to manifest version mismatch.",
- );
- return;
+ try {
+ // This will hard reload the destination path on navigations, or the
+ // current path on fetcher calls
+ if (
+ sessionStorage.getItem(MANIFEST_VERSION_STORAGE_KEY) ===
+ manifest.version
+ ) {
+ // We've already tried fixing for this version, don' try again to
+ // avoid loops - just let this navigation/fetch 404
+ console.error(
+ "Unable to discover routes due to manifest version mismatch.",
+ );
+ return;
+ }
+
+ sessionStorage.setItem(MANIFEST_VERSION_STORAGE_KEY, manifest.version);
+ } catch {
+ // Session storage unavailable
}
- sessionStorage.setItem(MANIFEST_VERSION_STORAGE_KEY, manifest.version);
window.location.href = errorReloadPath;
console.warn("Detected manifest version mismatch, reloading...");
@@ -291,7 +296,11 @@ export async function fetchAndApplyManifestPatches(
}
// Reset loop-detection on a successful response
- sessionStorage.removeItem(MANIFEST_VERSION_STORAGE_KEY);
+ try {
+ sessionStorage.removeItem(MANIFEST_VERSION_STORAGE_KEY);
+ } catch {
+ // Session storage unavailable
+ }
serverPatches = (await res.json()) as AssetsManifest["routes"];
} catch (e) {
if (signal?.aborted) return;