diff --git a/apps/sim/app/(landing)/blog/[slug]/page.tsx b/apps/sim/app/(landing)/blog/[slug]/page.tsx index d27f97e760d..42371752d9a 100644 --- a/apps/sim/app/(landing)/blog/[slug]/page.tsx +++ b/apps/sim/app/(landing)/blog/[slug]/page.tsx @@ -8,6 +8,7 @@ import { buildPostGraphJsonLd, buildPostMetadata } from '@/lib/blog/seo' import { getBaseUrl } from '@/lib/core/utils/urls' import { ShareButton } from '@/app/(landing)/blog/[slug]/share-button' import { BackLink } from '@/app/(landing)/components' +import { JsonLd } from '@/app/(landing)/components/json-ld' export const dynamicParams = false @@ -37,10 +38,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string return (
- `); `>` and `&` are + * escaped for completeness, and the JS line/paragraph separators (U+2028/U+2029) + * keep the payload valid inside inline scripts. + */ +const HTML_ESCAPES: Record = { + '<': '\\u003c', + '>': '\\u003e', + '&': '\\u0026', + [String.fromCharCode(0x2028)]: '\\u2028', + [String.fromCharCode(0x2029)]: '\\u2029', +} + +const UNSAFE_HTML_CHARS = new RegExp(`[<>&${String.fromCharCode(0x2028, 0x2029)}]`, 'g') + +/** + * Serialize structured data for an inline `application/ld+json` script. Plain + * `JSON.stringify` does not HTML-escape, so a `` (or stray `<`) in the + * data would break out of the script tag and become an XSS sink. Escaping these + * characters as unicode escapes keeps the JSON valid and semantically identical, + * so crawlers read the exact same graph — SEO output is unchanged. + */ +function serializeJsonLd(data: JsonLdData): string { + return JSON.stringify(data).replace(UNSAFE_HTML_CHARS, (char) => HTML_ESCAPES[char]) +} + +export type JsonLdData = Record + +interface JsonLdProps { + data: JsonLdData +} + +/** + * Server-rendered JSON-LD `