diff --git a/packages/fresh/src/runtime/client/partials.ts b/packages/fresh/src/runtime/client/partials.ts index 31eb59c0fec..f1c8c8844f7 100644 --- a/packages/fresh/src/runtime/client/partials.ts +++ b/packages/fresh/src/runtime/client/partials.ts @@ -411,7 +411,33 @@ export async function applyPartials(res: Response): Promise { } else if (child.nodeName === "SCRIPT") { const script = child as HTMLScriptElement; if (script.src === `${INTERNAL_PREFIX}/fresh-runtime.js`) return; - // TODO: What to do with script tags? + + // Append data scripts (e.g. application/ld+json for SEO structured + // data) to the document head. Skip executable script types to + // avoid unintended re-execution. + const t = script.type; + if ( + t !== "" && t !== "module" && t !== "text/javascript" && + t !== "importmap" + ) { + // Deduplicate: replace existing data script with same type and + // id, or same type and content, to avoid accumulating duplicates + // across repeated partial navigations. + const selector = script.id + ? `script[type="${t}"][id="${script.id}"]` + : `script[type="${t}"]`; + const existing = Array.from( + document.head.querySelectorAll(selector), + ).find((el) => + script.id ? true : el.textContent === script.textContent + ); + + if (existing === undefined) { + document.head.appendChild(script); + } else if (existing.textContent !== script.textContent) { + existing.textContent = script.textContent; + } + } } else if (child.nodeName === "STYLE") { const style = child as HTMLStyleElement; // TODO: Do we need a smarter merging strategy? diff --git a/packages/fresh/tests/partials_test.tsx b/packages/fresh/tests/partials_test.tsx index 81e47c0baa3..9bcf9376ba5 100644 --- a/packages/fresh/tests/partials_test.tsx +++ b/packages/fresh/tests/partials_test.tsx @@ -2907,3 +2907,151 @@ Deno.test({ } }, }); + +Deno.test({ + name: "partials - appends data scripts to head", + fn: async () => { + const app = testApp() + .get("/partial", (ctx) => { + return ctx.render( + + + {charset} + {favicon} + Updated +