Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fix-font-head-swap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'astro': patch
---

Fixes font flash (FOUT) during ClientRouter navigation by preserving inline `<style>` elements and font preload links in the head during page transitions.

Previously, `@font-face` declarations from the `<Font>` component were removed and re-inserted on every client-side navigation, causing the browser to re-evaluate them.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
import { ClientRouter } from 'astro:transitions';
---
<html>
<head>
<title>Font Page 1</title>
<style is:inline id="style">
@font-face {
font-family: 'TestFont';
src: url('/fonts/test.woff2') format('woff2');
font-display: swap;
}
body { font-family: 'TestFont', sans-serif; }
</style>
<link id="preload" rel="preload" href="/fonts/test.woff2" as="font" type="font/woff2" crossorigin />
<ClientRouter />
</head>
<body>
<p id="font-page-one">Font Page 1</p>
<a id="click-font-two" href="/font-page-two">go to font page 2</a>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
import { ClientRouter } from 'astro:transitions';
---
<html>
<head>
<title>Font Page 2</title>
<style is:inline>
@font-face {
font-family: 'TestFont';
src: url('/fonts/test.woff2') format('woff2');
font-display: swap;
}
body { font-family: 'TestFont', sans-serif; }
</style>
<link rel="preload" href="/fonts/test.woff2" as="font" type="font/woff2" crossorigin />
<ClientRouter />
</head>
<body>
<p id="font-page-two">Font Page 2</p>
<a id="click-font-one" href="/font-page-one">go to font page 1</a>
</body>
</html>
16 changes: 16 additions & 0 deletions packages/astro/e2e/view-transitions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1759,4 +1759,20 @@ test.describe('View Transitions', () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
expect(lines.join('')).toBe('');
});

test('Inline styles and font preloads persist through head swap', async ({ page, astro }) => {
// Go to font page 1
await page.goto(astro.resolveUrl('/font-page-one'));
let p = page.locator('#font-page-one');
await expect(p, 'should have content').toHaveText('Font Page 1');

// Navigate to font page 2 (same inline styles and font preloads)
await page.click('#click-font-two');
p = page.locator('#font-page-two');
await expect(p, 'should have content').toHaveText('Font Page 2');

// Verify original inline styles and font preloads are still present after navigation
await expect(page.locator('#style')).toHaveCount(1);
await expect(page.locator('#preload')).toHaveCount(1);
});
});
17 changes: 17 additions & 0 deletions packages/astro/src/transitions/swap-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,23 @@ const persistedHeadElement = (el: HTMLElement, newDoc: Document): Element | null
const href = el.getAttribute('href');
return newDoc.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
}
// Preserve inline <style> elements with identical content across navigations.
// This prevents unnecessary removal and re-insertion of styles (e.g. @font-face
// declarations from <Font>), which would cause the browser to re-evaluate them
// and trigger a flash of unstyled text (FOUT).
if (el.tagName === 'STYLE' && el.textContent) {
const styles = newDoc.head.querySelectorAll('style');
for (const s of styles) {
if (s.textContent === el.textContent) {
return s;
}
}
}
// Preserve font preload links across navigations to avoid re-fetching cached fonts.
if (el.matches('link[rel=preload][as=font]')) {
const href = el.getAttribute('href');
return newDoc.head.querySelector(`link[rel=preload][as=font][href="${href}"]`);
}
return null;
};

Expand Down
Loading