diff --git a/.changeset/angry-planets-admire.md b/.changeset/angry-planets-admire.md
new file mode 100644
index 0000000000..9ff45066e8
--- /dev/null
+++ b/.changeset/angry-planets-admire.md
@@ -0,0 +1,5 @@
+---
+"react-router": patch
+---
+
+Escape HTML in `meta()` JSON-LD content
diff --git a/packages/react-router/__tests__/dom/ssr/meta-test.tsx b/packages/react-router/__tests__/dom/ssr/meta-test.tsx
index 7bf430bca7..3e5783b28f 100644
--- a/packages/react-router/__tests__/dom/ssr/meta-test.tsx
+++ b/packages/react-router/__tests__/dom/ssr/meta-test.tsx
@@ -251,6 +251,7 @@ describe("meta", () => {
postalCode: "92107",
},
email: ["sonnyday@fancymail.com", "surfergal@veryprofessional.org"],
+ bio: "A surfer & coder.",
};
let RoutesStub = createRoutesStub([
@@ -273,6 +274,9 @@ describe("meta", () => {
container.querySelector('script[type="application/ld+json"]')
?.innerHTML || "{}";
expect(JSON.parse(scriptTagContents)).toEqual(jsonLd);
+ expect(scriptTagContents).toContain(
+ "A \\u003cb\\u003esurfer\\u003c/b\\u003e \\u0026 \\u003cem\\u003ecoder\\u003c/em\\u003e.",
+ );
});
it("{ tagName: 'link' } adds a ", () => {
diff --git a/packages/react-router/__tests__/server-runtime/markup-test.ts b/packages/react-router/__tests__/server-runtime/markup-test.ts
index 4eb0d8356f..401c4f3bae 100644
--- a/packages/react-router/__tests__/server-runtime/markup-test.ts
+++ b/packages/react-router/__tests__/server-runtime/markup-test.ts
@@ -1,6 +1,6 @@
import vm from "vm";
-import { escapeHtml } from "../../lib/server-runtime/markup";
+import { escapeHtml } from "../../lib/dom/ssr/markup";
describe("escapeHtml", () => {
// These tests are based on https://github.com/zertosh/htmlescape/blob/3e6cf0614dd0f778fd0131e69070b77282150c15/test/htmlescape-test.js
diff --git a/packages/react-router/lib/dom/ssr/components.tsx b/packages/react-router/lib/dom/ssr/components.tsx
index 85271fecf8..318fa0bf51 100644
--- a/packages/react-router/lib/dom/ssr/components.tsx
+++ b/packages/react-router/lib/dom/ssr/components.tsx
@@ -19,7 +19,7 @@ import {
isPageLinkDescriptor,
} from "./links";
import type { KeyedHtmlLinkDescriptor } from "./links";
-import { createHtml } from "./markup";
+import { escapeHtml } from "./markup";
import type {
MetaFunction,
MetaDescriptor,
@@ -629,7 +629,7 @@ export function Meta(): React.JSX.Element {
);
} catch (err) {
@@ -860,13 +860,13 @@ import(${JSON.stringify(manifest.entry.module)});`;
diff --git a/packages/react-router/lib/dom/ssr/markup.ts b/packages/react-router/lib/dom/ssr/markup.ts
index 49896900ef..4ab1fdcc78 100644
--- a/packages/react-router/lib/dom/ssr/markup.ts
+++ b/packages/react-router/lib/dom/ssr/markup.ts
@@ -17,11 +17,3 @@ const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
export function escapeHtml(html: string) {
return html.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
}
-
-export interface SafeHtml {
- __html: string;
-}
-
-export function createHtml(html: string): SafeHtml {
- return { __html: html };
-}
diff --git a/packages/react-router/lib/server-runtime/markup.ts b/packages/react-router/lib/server-runtime/markup.ts
deleted file mode 100644
index 4ab1fdcc78..0000000000
--- a/packages/react-router/lib/server-runtime/markup.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-// This escapeHtml utility is based on https://github.com/zertosh/htmlescape
-// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
-
-// We've chosen to inline the utility here to reduce the number of npm dependencies we have,
-// slightly decrease the code size compared the original package and make it esm compatible.
-
-const ESCAPE_LOOKUP: { [match: string]: string } = {
- "&": "\\u0026",
- ">": "\\u003e",
- "<": "\\u003c",
- "\u2028": "\\u2028",
- "\u2029": "\\u2029",
-};
-
-const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
-
-export function escapeHtml(html: string) {
- return html.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
-}
diff --git a/packages/react-router/lib/server-runtime/serverHandoff.ts b/packages/react-router/lib/server-runtime/serverHandoff.ts
index 1df858cbe6..09dec1477c 100644
--- a/packages/react-router/lib/server-runtime/serverHandoff.ts
+++ b/packages/react-router/lib/server-runtime/serverHandoff.ts
@@ -1,5 +1,5 @@
import type { CriticalCss, FutureConfig } from "../dom/ssr/entry";
-import { escapeHtml } from "./markup";
+import { escapeHtml } from "../dom/ssr/markup";
import type { ServerBuild } from "./build";
export type ServerHandoff = {