diff --git a/src/PolicyEngine.jsx b/src/PolicyEngine.jsx
index 8a4ed9824..721334f5c 100644
--- a/src/PolicyEngine.jsx
+++ b/src/PolicyEngine.jsx
@@ -45,6 +45,7 @@ import style from "./style";
import RedirectToCountry from "./routing/RedirectToCountry";
import CountryIdLayout from "./routing/CountryIdLayout";
import RedirectBlogPost from "./routing/RedirectBlogPost";
+import ExternalRedirect from "./routing/ExternalRedirect";
import { StatusPage } from "./pages/StatusPage";
import ManifestosComparison from "./applets/ManifestosComparison";
import DeveloperLayout from "./pages/DeveloperLayout";
@@ -346,6 +347,11 @@ export default function PolicyEngine() {
} />
} />
} />
+ {/* Vanity redirect: /[countryId]/policybench -> policybench.org */}
+ }
+ />
} />
} />
+ {/* Vanity redirect: bare /policybench -> policybench.org */}
+ }
+ />
} />
} />
{
+ const replaceMock = jest.fn();
+ let originalLocation;
+
+ beforeAll(() => {
+ originalLocation = window.location;
+ Object.defineProperty(window, "location", {
+ configurable: true,
+ writable: true,
+ value: { replace: replaceMock, href: "http://localhost/" },
+ });
+ });
+
+ afterAll(() => {
+ Object.defineProperty(window, "location", {
+ configurable: true,
+ writable: true,
+ value: originalLocation,
+ });
+ });
+
+ beforeEach(() => replaceMock.mockClear());
+
+ test("redirects the browser to the external URL", () => {
+ render();
+ expect(replaceMock).toHaveBeenCalledWith("https://policybench.org");
+ });
+
+ test("renders a fallback link to the destination", () => {
+ render();
+ expect(screen.getByRole("link").getAttribute("href")).toBe(
+ "https://policybench.org",
+ );
+ });
+});
diff --git a/src/routing/ExternalRedirect.jsx b/src/routing/ExternalRedirect.jsx
new file mode 100644
index 000000000..184e0f3ca
--- /dev/null
+++ b/src/routing/ExternalRedirect.jsx
@@ -0,0 +1,21 @@
+import { useEffect } from "react";
+
+/**
+ * Redirects the browser to an external URL outside the SPA's router.
+ *
+ * react-router's only resolves in-app paths, so vanity routes
+ * that point at another domain (e.g. /policybench -> https://policybench.org)
+ * use this instead. Renders a short fallback link in case the redirect is
+ * slow or scripting is disabled.
+ */
+export default function ExternalRedirect({ to }) {
+ useEffect(() => {
+ window.location.replace(to);
+ }, [to]);
+
+ return (
+
+ Redirecting to {to}…
+
+ );
+}