diff --git a/.changeset/popular-horses-lie.md b/.changeset/popular-horses-lie.md new file mode 100644 index 000000000..6d9ffdbbd --- /dev/null +++ b/.changeset/popular-horses-lie.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Prevent multiple rendering of head content diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 6a392f840..c423a1abf 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1212,6 +1212,7 @@ export interface SSRMetadata { pathname: string; hasHydrationScript: boolean; hasDirectives: Set; + hasRenderedHead: boolean; } export interface SSRResult { diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts index 9eb618cb3..d4704ca1f 100644 --- a/packages/astro/src/core/render/result.ts +++ b/packages/astro/src/core/render/result.ts @@ -270,6 +270,7 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site); renderers, pathname, hasHydrationScript: false, + hasRenderedHead: false, hasDirectives: new Set(), }, response, diff --git a/packages/astro/src/runtime/server/render/head.ts b/packages/astro/src/runtime/server/render/head.ts index ed4b73f3e..9afed33fe 100644 --- a/packages/astro/src/runtime/server/render/head.ts +++ b/packages/astro/src/runtime/server/render/head.ts @@ -12,9 +12,8 @@ const uniqueElements = (item: any, index: number, all: any[]) => { ); }; -const alreadyHeadRenderedResults = new WeakSet(); export function renderHead(result: SSRResult): Promise { - alreadyHeadRenderedResults.add(result); + result._metadata.hasRenderedHead = true; const styles = Array.from(result.styles) .filter(uniqueElements) .map((style) => renderElement('style', style)); @@ -36,7 +35,7 @@ export function renderHead(result: SSRResult): Promise { // is called before a component's first non-head HTML element. If the head was // already injected it is a noop. export async function* maybeRenderHead(result: SSRResult): AsyncIterable { - if (alreadyHeadRenderedResults.has(result)) { + if (result._metadata.hasRenderedHead) { return; } yield renderHead(result); diff --git a/packages/astro/test/fixtures/lazy-layout/package.json b/packages/astro/test/fixtures/lazy-layout/package.json new file mode 100644 index 000000000..4fe627073 --- /dev/null +++ b/packages/astro/test/fixtures/lazy-layout/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/lazy-layout", + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/lazy-layout/src/layouts/Main.astro b/packages/astro/test/fixtures/lazy-layout/src/layouts/Main.astro new file mode 100644 index 000000000..d19faadcb --- /dev/null +++ b/packages/astro/test/fixtures/lazy-layout/src/layouts/Main.astro @@ -0,0 +1,13 @@ + + + Testing + + + + + + diff --git a/packages/astro/test/fixtures/lazy-layout/src/pages/index.astro b/packages/astro/test/fixtures/lazy-layout/src/pages/index.astro new file mode 100644 index 000000000..603057e53 --- /dev/null +++ b/packages/astro/test/fixtures/lazy-layout/src/pages/index.astro @@ -0,0 +1,6 @@ +--- +const Layout = (await import('../layouts/Main.astro')).default; +--- + +
Stuff here
+
diff --git a/packages/astro/test/lazy-layout.test.js b/packages/astro/test/lazy-layout.test.js new file mode 100644 index 000000000..4ed22f5a0 --- /dev/null +++ b/packages/astro/test/lazy-layout.test.js @@ -0,0 +1,19 @@ +import { expect } from 'chai'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('Lazily imported layouts', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ root: './fixtures/lazy-layout/' }); + await fixture.build(); + }); + + it('Renders styles only once', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + expect($('link')).to.have.a.lengthOf(1); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb386d8d9..e12b7f63b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1664,6 +1664,12 @@ importers: '@astrojs/solid-js': link:../../../../integrations/solid astro: link:../../.. + packages/astro/test/fixtures/lazy-layout: + specifiers: + astro: workspace:* + dependencies: + astro: link:../../.. + packages/astro/test/fixtures/legacy-astro-flavored-markdown: specifiers: '@astrojs/preact': workspace:*