@reblu/site-next 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @reblu/site-next
2
2
 
3
- Next.js glue for Reblu SITEs (REB-418, REB-510). Three responsibilities, all
3
+ Next.js glue for Reblu SITEs (REB-418, REB-510). Four responsibilities, all
4
4
  intentionally Next-coupled so the lower SDK layers —
5
5
  [`@reblu/site-client`](../site-client), [`@reblu/site-contracts`](../site-contracts),
6
6
  [`@reblu/site-ui`](../site-ui) — stay framework-agnostic:
@@ -13,6 +13,8 @@ intentionally Next-coupled so the lower SDK layers —
13
13
  client auth components (`LoginForm`, password reset, set-password,
14
14
  email confirm, MFA step-up, `AuthLogo`) and the server-only Route Handler
15
15
  factory that proxies them to base-api's `/api/site/auth/*`.
16
+ 4. **`<JsonLd>`** (`./json-ld`) — safe `application/ld+json` `<script>`
17
+ rendering; see "Structured data (JSON-LD)" below.
16
18
 
17
19
  ## Install
18
20
 
@@ -123,6 +125,39 @@ export const POST = createProxyRoute('/api/site/auth/login')
123
125
  `REBLU_API_BASE_URL`/`REBLU_API_KEY` env vars — read lazily per request, not at
124
126
  module load.
125
127
 
128
+ ## Structured data (JSON-LD)
129
+
130
+ `@reblu/site-next/json-ld` (REB-622, server-only, dependency-free) renders one
131
+ `application/ld+json` `<script>` tag the safe way:
132
+
133
+ ```tsx
134
+ import { JsonLd } from '@reblu/site-next/json-ld'
135
+
136
+ <JsonLd id='page' data={page.jsonLd} />
137
+ ```
138
+
139
+ A `<script>` body is HTML's **raw-text** context: the parser ends it on the
140
+ first `</script` byte sequence it sees, no matter whether that sequence sits
141
+ inside a JSON string. `JSON.stringify(data)` alone leaves `<` verbatim, so
142
+ tenant-authored data serialised straight into a JSON-LD `<script>` is a
143
+ stored-XSS sink — a title containing `</script><img src=x onerror=alert(1)>`
144
+ breaks out of the element and runs. `jsonLdScriptContent(value)`, which
145
+ `<JsonLd>` wraps, escapes `<`, `>`, `&`, U+2028 and U+2029 to their `\uXXXX`
146
+ JSON form: every escape is a standard JSON character escape, so
147
+ `JSON.parse(jsonLdScriptContent(value))` always recovers `value` unchanged.
148
+ This is an **escaper**, not the strip/allowlist **sanitiser**
149
+ [`sanitizeHtml`](../site-blocks#security-boundaries) in `@reblu/site-blocks`
150
+ covers — that one guards HTML rendered via `dangerouslySetInnerHTML`; this one
151
+ guards the JSON-LD script boundary, a different sink `sanitizeHtml` was never
152
+ built to handle.
153
+
154
+ `<JsonLd>` ships from its own tsup entry, isolated from the root `index`
155
+ barrel the same way `./cache` and `./route-handlers` are: it has zero
156
+ dependencies of its own (no `@reblu/site-ui`, no `next/link`), so importing it
157
+ must never drag in `CtaButtons`/`EditableCta`'s client-component graph.
158
+ Measured: `dist/json-ld.js` is 701 B (451 B gzipped) and its only runtime
159
+ import is `react/jsx-runtime` — no parser, no sanitiser chain.
160
+
126
161
  ## Build
127
162
 
128
163
  `tsup` → dual ESM/CJS + `.d.ts`. Code-splitting is **off** so each entry keeps its
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/json-ld.tsx
21
+ var json_ld_exports = {};
22
+ __export(json_ld_exports, {
23
+ JsonLd: () => JsonLd,
24
+ jsonLdScriptContent: () => jsonLdScriptContent
25
+ });
26
+ module.exports = __toCommonJS(json_ld_exports);
27
+ var import_jsx_runtime = require("react/jsx-runtime");
28
+ var SCRIPT_BOUNDARY_UNSAFE = /[<>&\u2028\u2029]/g;
29
+ var ESCAPES = {
30
+ "<": "\\u003c",
31
+ ">": "\\u003e",
32
+ "&": "\\u0026",
33
+ "\u2028": "\\u2028",
34
+ "\u2029": "\\u2029"
35
+ };
36
+ function jsonLdScriptContent(value) {
37
+ const json = JSON.stringify(value) ?? "null";
38
+ return json.replace(SCRIPT_BOUNDARY_UNSAFE, (char) => ESCAPES[char]);
39
+ }
40
+ function JsonLd({ id, data }) {
41
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
42
+ "script",
43
+ {
44
+ id: `json-ld-${id}`,
45
+ type: "application/ld+json",
46
+ suppressHydrationWarning: true,
47
+ dangerouslySetInnerHTML: { __html: jsonLdScriptContent(data) }
48
+ }
49
+ );
50
+ }
51
+ // Annotate the CommonJS export names for ESM import in node:
52
+ 0 && (module.exports = {
53
+ JsonLd,
54
+ jsonLdScriptContent
55
+ });
56
+ //# sourceMappingURL=json-ld.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/json-ld.tsx"],"sourcesContent":["/**\n * `<JsonLd>` — safe `application/ld+json` script renderer (REB-622).\n *\n * A `<script>` element is HTML's \"raw text\" context: the parser closes it on the\n * first case-insensitive `</script` byte sequence it sees, no matter whether\n * that sequence sits inside a JSON string. `JSON.stringify` escapes for JSON\n * syntax, not for this HTML boundary, so it leaves `<` verbatim — turning any\n * JSON-LD payload built from tenant-authored data (page title, description,\n * FAQ copy, …) into a stored-XSS sink: a value containing\n * `</script><img src=x onerror=alert(1)>` breaks out of the element and runs.\n *\n * `jsonLdScriptContent` neutralises exactly that boundary while staying\n * byte-for-byte valid JSON: `<` / `>` / `&` are standard JSON\n * character escapes, so `JSON.parse` recovers the original value unchanged —\n * this is an ESCAPER, not a strip/allowlist sanitiser (see `sanitizeHtml` in\n * `@reblu/site-blocks` for that job, at the HTML boundary rather than the\n * script boundary). U+2028/U+2029 are escaped too: both are valid inside a\n * JSON string but are raw JS line terminators, and some `<script>` consumers\n * (older JS parsers, some static analysers) choke on them unescaped.\n *\n * This package, not `@reblu/site-blocks`, is the home for it: `site-blocks`'s\n * main entry bundles `parse5` + the `hast-util-*` chain + `marked` for\n * `sanitizeHtml` with `treeshake: false` — dist/index.js is ~463 KB, and PR #848\n * spent a full round removing exactly that weight (89 KB gzipped) from a client\n * bundle that only needed one HTML-escaping helper. `<JsonLd>` is Next-specific\n * (App Router's `suppressHydrationWarning` pattern) and this 40-line escaper has\n * zero dependencies of its own, so it ships as its OWN tsup entry\n * (`@reblu/site-next/json-ld`) rather than folding into the root `index` barrel\n * — importing it must never drag in `CtaButtons`/`EditableCta`'s `next/link` +\n * `@reblu/site-ui` graph, the same isolation `./cache` and `./route-handlers`\n * already use for the same reason.\n *\n * @see https://nextjs.org/docs/app/guides/json-ld\n */\n\nconst SCRIPT_BOUNDARY_UNSAFE = /[<>&\\u2028\\u2029]/g\n\nconst ESCAPES: Record<string, string> = {\n '<': '\\\\u003c',\n '>': '\\\\u003e',\n '&': '\\\\u0026',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n}\n\n/**\n * Serialises `value` for embedding inside `<script type=\"application/ld+json\">`.\n * Escapes `<`, `>`, `&`, U+2028 and U+2029 — every escape is a standard JSON\n * character escape, so `JSON.parse(jsonLdScriptContent(value))` always recovers\n * `value` unchanged. This only ever ADDS escaping; it never strips or rewrites\n * data, unlike an HTML sanitiser.\n *\n * `JSON.stringify(undefined)` returns the JS value `undefined`, not a string\n * (`undefined` has no JSON representation), which would otherwise throw here.\n * `data` is typed `unknown` with zero friction toward `undefined`, so this\n * falls back to the JSON literal `'null'` rather than crashing an entire page\n * render over one missing field.\n */\nexport function jsonLdScriptContent(value: unknown): string {\n const json = JSON.stringify(value) ?? 'null'\n return json.replace(SCRIPT_BOUNDARY_UNSAFE, (char) => ESCAPES[char])\n}\n\nexport interface JsonLdProps {\n /** Suffix for the rendered `<script>` id: `json-ld-${id}`. */\n id: string\n /** Structured data to serialise. Any JSON-serialisable value. */\n data: unknown\n}\n\n/**\n * Renders one `application/ld+json` `<script>` tag using the official Next.js\n * App Router pattern. `suppressHydrationWarning` is required here: React warns\n * on a server/client `textContent` mismatch for this element even though there\n * is no real divergence — `dangerouslySetInnerHTML` content is opaque to React's\n * hydration diff.\n */\nexport function JsonLd({ id, data }: JsonLdProps) {\n return (\n <script\n id={`json-ld-${id}`}\n type='application/ld+json'\n suppressHydrationWarning\n dangerouslySetInnerHTML={{ __html: jsonLdScriptContent(data) }}\n />\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+EI;AA5CJ,IAAM,yBAAyB;AAE/B,IAAM,UAAkC;AAAA,EACtC,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,UAAU;AAAA,EACV,UAAU;AACZ;AAeO,SAAS,oBAAoB,OAAwB;AAC1D,QAAM,OAAO,KAAK,UAAU,KAAK,KAAK;AACtC,SAAO,KAAK,QAAQ,wBAAwB,CAAC,SAAS,QAAQ,IAAI,CAAC;AACrE;AAgBO,SAAS,OAAO,EAAE,IAAI,KAAK,GAAgB;AAChD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,IAAI,WAAW,EAAE;AAAA,MACjB,MAAK;AAAA,MACL,0BAAwB;AAAA,MACxB,yBAAyB,EAAE,QAAQ,oBAAoB,IAAI,EAAE;AAAA;AAAA,EAC/D;AAEJ;","names":[]}
@@ -0,0 +1,66 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * `<JsonLd>` — safe `application/ld+json` script renderer (REB-622).
5
+ *
6
+ * A `<script>` element is HTML's "raw text" context: the parser closes it on the
7
+ * first case-insensitive `</script` byte sequence it sees, no matter whether
8
+ * that sequence sits inside a JSON string. `JSON.stringify` escapes for JSON
9
+ * syntax, not for this HTML boundary, so it leaves `<` verbatim — turning any
10
+ * JSON-LD payload built from tenant-authored data (page title, description,
11
+ * FAQ copy, …) into a stored-XSS sink: a value containing
12
+ * `</script><img src=x onerror=alert(1)>` breaks out of the element and runs.
13
+ *
14
+ * `jsonLdScriptContent` neutralises exactly that boundary while staying
15
+ * byte-for-byte valid JSON: `<` / `>` / `&` are standard JSON
16
+ * character escapes, so `JSON.parse` recovers the original value unchanged —
17
+ * this is an ESCAPER, not a strip/allowlist sanitiser (see `sanitizeHtml` in
18
+ * `@reblu/site-blocks` for that job, at the HTML boundary rather than the
19
+ * script boundary). U+2028/U+2029 are escaped too: both are valid inside a
20
+ * JSON string but are raw JS line terminators, and some `<script>` consumers
21
+ * (older JS parsers, some static analysers) choke on them unescaped.
22
+ *
23
+ * This package, not `@reblu/site-blocks`, is the home for it: `site-blocks`'s
24
+ * main entry bundles `parse5` + the `hast-util-*` chain + `marked` for
25
+ * `sanitizeHtml` with `treeshake: false` — dist/index.js is ~463 KB, and PR #848
26
+ * spent a full round removing exactly that weight (89 KB gzipped) from a client
27
+ * bundle that only needed one HTML-escaping helper. `<JsonLd>` is Next-specific
28
+ * (App Router's `suppressHydrationWarning` pattern) and this 40-line escaper has
29
+ * zero dependencies of its own, so it ships as its OWN tsup entry
30
+ * (`@reblu/site-next/json-ld`) rather than folding into the root `index` barrel
31
+ * — importing it must never drag in `CtaButtons`/`EditableCta`'s `next/link` +
32
+ * `@reblu/site-ui` graph, the same isolation `./cache` and `./route-handlers`
33
+ * already use for the same reason.
34
+ *
35
+ * @see https://nextjs.org/docs/app/guides/json-ld
36
+ */
37
+ /**
38
+ * Serialises `value` for embedding inside `<script type="application/ld+json">`.
39
+ * Escapes `<`, `>`, `&`, U+2028 and U+2029 — every escape is a standard JSON
40
+ * character escape, so `JSON.parse(jsonLdScriptContent(value))` always recovers
41
+ * `value` unchanged. This only ever ADDS escaping; it never strips or rewrites
42
+ * data, unlike an HTML sanitiser.
43
+ *
44
+ * `JSON.stringify(undefined)` returns the JS value `undefined`, not a string
45
+ * (`undefined` has no JSON representation), which would otherwise throw here.
46
+ * `data` is typed `unknown` with zero friction toward `undefined`, so this
47
+ * falls back to the JSON literal `'null'` rather than crashing an entire page
48
+ * render over one missing field.
49
+ */
50
+ declare function jsonLdScriptContent(value: unknown): string;
51
+ interface JsonLdProps {
52
+ /** Suffix for the rendered `<script>` id: `json-ld-${id}`. */
53
+ id: string;
54
+ /** Structured data to serialise. Any JSON-serialisable value. */
55
+ data: unknown;
56
+ }
57
+ /**
58
+ * Renders one `application/ld+json` `<script>` tag using the official Next.js
59
+ * App Router pattern. `suppressHydrationWarning` is required here: React warns
60
+ * on a server/client `textContent` mismatch for this element even though there
61
+ * is no real divergence — `dangerouslySetInnerHTML` content is opaque to React's
62
+ * hydration diff.
63
+ */
64
+ declare function JsonLd({ id, data }: JsonLdProps): react_jsx_runtime.JSX.Element;
65
+
66
+ export { JsonLd, type JsonLdProps, jsonLdScriptContent };
@@ -0,0 +1,66 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * `<JsonLd>` — safe `application/ld+json` script renderer (REB-622).
5
+ *
6
+ * A `<script>` element is HTML's "raw text" context: the parser closes it on the
7
+ * first case-insensitive `</script` byte sequence it sees, no matter whether
8
+ * that sequence sits inside a JSON string. `JSON.stringify` escapes for JSON
9
+ * syntax, not for this HTML boundary, so it leaves `<` verbatim — turning any
10
+ * JSON-LD payload built from tenant-authored data (page title, description,
11
+ * FAQ copy, …) into a stored-XSS sink: a value containing
12
+ * `</script><img src=x onerror=alert(1)>` breaks out of the element and runs.
13
+ *
14
+ * `jsonLdScriptContent` neutralises exactly that boundary while staying
15
+ * byte-for-byte valid JSON: `<` / `>` / `&` are standard JSON
16
+ * character escapes, so `JSON.parse` recovers the original value unchanged —
17
+ * this is an ESCAPER, not a strip/allowlist sanitiser (see `sanitizeHtml` in
18
+ * `@reblu/site-blocks` for that job, at the HTML boundary rather than the
19
+ * script boundary). U+2028/U+2029 are escaped too: both are valid inside a
20
+ * JSON string but are raw JS line terminators, and some `<script>` consumers
21
+ * (older JS parsers, some static analysers) choke on them unescaped.
22
+ *
23
+ * This package, not `@reblu/site-blocks`, is the home for it: `site-blocks`'s
24
+ * main entry bundles `parse5` + the `hast-util-*` chain + `marked` for
25
+ * `sanitizeHtml` with `treeshake: false` — dist/index.js is ~463 KB, and PR #848
26
+ * spent a full round removing exactly that weight (89 KB gzipped) from a client
27
+ * bundle that only needed one HTML-escaping helper. `<JsonLd>` is Next-specific
28
+ * (App Router's `suppressHydrationWarning` pattern) and this 40-line escaper has
29
+ * zero dependencies of its own, so it ships as its OWN tsup entry
30
+ * (`@reblu/site-next/json-ld`) rather than folding into the root `index` barrel
31
+ * — importing it must never drag in `CtaButtons`/`EditableCta`'s `next/link` +
32
+ * `@reblu/site-ui` graph, the same isolation `./cache` and `./route-handlers`
33
+ * already use for the same reason.
34
+ *
35
+ * @see https://nextjs.org/docs/app/guides/json-ld
36
+ */
37
+ /**
38
+ * Serialises `value` for embedding inside `<script type="application/ld+json">`.
39
+ * Escapes `<`, `>`, `&`, U+2028 and U+2029 — every escape is a standard JSON
40
+ * character escape, so `JSON.parse(jsonLdScriptContent(value))` always recovers
41
+ * `value` unchanged. This only ever ADDS escaping; it never strips or rewrites
42
+ * data, unlike an HTML sanitiser.
43
+ *
44
+ * `JSON.stringify(undefined)` returns the JS value `undefined`, not a string
45
+ * (`undefined` has no JSON representation), which would otherwise throw here.
46
+ * `data` is typed `unknown` with zero friction toward `undefined`, so this
47
+ * falls back to the JSON literal `'null'` rather than crashing an entire page
48
+ * render over one missing field.
49
+ */
50
+ declare function jsonLdScriptContent(value: unknown): string;
51
+ interface JsonLdProps {
52
+ /** Suffix for the rendered `<script>` id: `json-ld-${id}`. */
53
+ id: string;
54
+ /** Structured data to serialise. Any JSON-serialisable value. */
55
+ data: unknown;
56
+ }
57
+ /**
58
+ * Renders one `application/ld+json` `<script>` tag using the official Next.js
59
+ * App Router pattern. `suppressHydrationWarning` is required here: React warns
60
+ * on a server/client `textContent` mismatch for this element even though there
61
+ * is no real divergence — `dangerouslySetInnerHTML` content is opaque to React's
62
+ * hydration diff.
63
+ */
64
+ declare function JsonLd({ id, data }: JsonLdProps): react_jsx_runtime.JSX.Element;
65
+
66
+ export { JsonLd, type JsonLdProps, jsonLdScriptContent };
@@ -0,0 +1,30 @@
1
+ // src/json-ld.tsx
2
+ import { jsx } from "react/jsx-runtime";
3
+ var SCRIPT_BOUNDARY_UNSAFE = /[<>&\u2028\u2029]/g;
4
+ var ESCAPES = {
5
+ "<": "\\u003c",
6
+ ">": "\\u003e",
7
+ "&": "\\u0026",
8
+ "\u2028": "\\u2028",
9
+ "\u2029": "\\u2029"
10
+ };
11
+ function jsonLdScriptContent(value) {
12
+ const json = JSON.stringify(value) ?? "null";
13
+ return json.replace(SCRIPT_BOUNDARY_UNSAFE, (char) => ESCAPES[char]);
14
+ }
15
+ function JsonLd({ id, data }) {
16
+ return /* @__PURE__ */ jsx(
17
+ "script",
18
+ {
19
+ id: `json-ld-${id}`,
20
+ type: "application/ld+json",
21
+ suppressHydrationWarning: true,
22
+ dangerouslySetInnerHTML: { __html: jsonLdScriptContent(data) }
23
+ }
24
+ );
25
+ }
26
+ export {
27
+ JsonLd,
28
+ jsonLdScriptContent
29
+ };
30
+ //# sourceMappingURL=json-ld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/json-ld.tsx"],"sourcesContent":["/**\n * `<JsonLd>` — safe `application/ld+json` script renderer (REB-622).\n *\n * A `<script>` element is HTML's \"raw text\" context: the parser closes it on the\n * first case-insensitive `</script` byte sequence it sees, no matter whether\n * that sequence sits inside a JSON string. `JSON.stringify` escapes for JSON\n * syntax, not for this HTML boundary, so it leaves `<` verbatim — turning any\n * JSON-LD payload built from tenant-authored data (page title, description,\n * FAQ copy, …) into a stored-XSS sink: a value containing\n * `</script><img src=x onerror=alert(1)>` breaks out of the element and runs.\n *\n * `jsonLdScriptContent` neutralises exactly that boundary while staying\n * byte-for-byte valid JSON: `<` / `>` / `&` are standard JSON\n * character escapes, so `JSON.parse` recovers the original value unchanged —\n * this is an ESCAPER, not a strip/allowlist sanitiser (see `sanitizeHtml` in\n * `@reblu/site-blocks` for that job, at the HTML boundary rather than the\n * script boundary). U+2028/U+2029 are escaped too: both are valid inside a\n * JSON string but are raw JS line terminators, and some `<script>` consumers\n * (older JS parsers, some static analysers) choke on them unescaped.\n *\n * This package, not `@reblu/site-blocks`, is the home for it: `site-blocks`'s\n * main entry bundles `parse5` + the `hast-util-*` chain + `marked` for\n * `sanitizeHtml` with `treeshake: false` — dist/index.js is ~463 KB, and PR #848\n * spent a full round removing exactly that weight (89 KB gzipped) from a client\n * bundle that only needed one HTML-escaping helper. `<JsonLd>` is Next-specific\n * (App Router's `suppressHydrationWarning` pattern) and this 40-line escaper has\n * zero dependencies of its own, so it ships as its OWN tsup entry\n * (`@reblu/site-next/json-ld`) rather than folding into the root `index` barrel\n * — importing it must never drag in `CtaButtons`/`EditableCta`'s `next/link` +\n * `@reblu/site-ui` graph, the same isolation `./cache` and `./route-handlers`\n * already use for the same reason.\n *\n * @see https://nextjs.org/docs/app/guides/json-ld\n */\n\nconst SCRIPT_BOUNDARY_UNSAFE = /[<>&\\u2028\\u2029]/g\n\nconst ESCAPES: Record<string, string> = {\n '<': '\\\\u003c',\n '>': '\\\\u003e',\n '&': '\\\\u0026',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n}\n\n/**\n * Serialises `value` for embedding inside `<script type=\"application/ld+json\">`.\n * Escapes `<`, `>`, `&`, U+2028 and U+2029 — every escape is a standard JSON\n * character escape, so `JSON.parse(jsonLdScriptContent(value))` always recovers\n * `value` unchanged. This only ever ADDS escaping; it never strips or rewrites\n * data, unlike an HTML sanitiser.\n *\n * `JSON.stringify(undefined)` returns the JS value `undefined`, not a string\n * (`undefined` has no JSON representation), which would otherwise throw here.\n * `data` is typed `unknown` with zero friction toward `undefined`, so this\n * falls back to the JSON literal `'null'` rather than crashing an entire page\n * render over one missing field.\n */\nexport function jsonLdScriptContent(value: unknown): string {\n const json = JSON.stringify(value) ?? 'null'\n return json.replace(SCRIPT_BOUNDARY_UNSAFE, (char) => ESCAPES[char])\n}\n\nexport interface JsonLdProps {\n /** Suffix for the rendered `<script>` id: `json-ld-${id}`. */\n id: string\n /** Structured data to serialise. Any JSON-serialisable value. */\n data: unknown\n}\n\n/**\n * Renders one `application/ld+json` `<script>` tag using the official Next.js\n * App Router pattern. `suppressHydrationWarning` is required here: React warns\n * on a server/client `textContent` mismatch for this element even though there\n * is no real divergence — `dangerouslySetInnerHTML` content is opaque to React's\n * hydration diff.\n */\nexport function JsonLd({ id, data }: JsonLdProps) {\n return (\n <script\n id={`json-ld-${id}`}\n type='application/ld+json'\n suppressHydrationWarning\n dangerouslySetInnerHTML={{ __html: jsonLdScriptContent(data) }}\n />\n )\n}\n"],"mappings":";AA+EI;AA5CJ,IAAM,yBAAyB;AAE/B,IAAM,UAAkC;AAAA,EACtC,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,UAAU;AAAA,EACV,UAAU;AACZ;AAeO,SAAS,oBAAoB,OAAwB;AAC1D,QAAM,OAAO,KAAK,UAAU,KAAK,KAAK;AACtC,SAAO,KAAK,QAAQ,wBAAwB,CAAC,SAAS,QAAQ,IAAI,CAAC;AACrE;AAgBO,SAAS,OAAO,EAAE,IAAI,KAAK,GAAgB;AAChD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,IAAI,WAAW,EAAE;AAAA,MACjB,MAAK;AAAA,MACL,0BAAwB;AAAA,MACxB,yBAAyB,EAAE,QAAQ,oBAAoB,IAAI,EAAE;AAAA;AAAA,EAC/D;AAEJ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reblu/site-next",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Next.js RSC glue for Reblu SITEs — cache + CTA + auth server/client components.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Reblu — Adolfo Unturbe",
@@ -80,6 +80,16 @@
80
80
  "types": "./dist/route-handlers.d.cts",
81
81
  "default": "./dist/route-handlers.cjs"
82
82
  }
83
+ },
84
+ "./json-ld": {
85
+ "import": {
86
+ "types": "./dist/json-ld.d.ts",
87
+ "default": "./dist/json-ld.js"
88
+ },
89
+ "require": {
90
+ "types": "./dist/json-ld.d.cts",
91
+ "default": "./dist/json-ld.cjs"
92
+ }
83
93
  }
84
94
  },
85
95
  "publishConfig": {
@@ -91,9 +101,9 @@
91
101
  "react": ">=19"
92
102
  },
93
103
  "dependencies": {
94
- "@reblu/site-client": "^1.0.0",
104
+ "@reblu/site-client": "^3.0.0",
95
105
  "@reblu/site-ui": "^0.3.1",
96
- "@reblu/site-contracts": "^0.2.0"
106
+ "@reblu/site-contracts": "^0.4.0"
97
107
  },
98
108
  "devDependencies": {
99
109
  "@testing-library/jest-dom": "^6.9.1",