diff --git a/.changeset/khaki-ducks-give.md b/.changeset/khaki-ducks-give.md
new file mode 100644
index 000000000000..3f759891b824
--- /dev/null
+++ b/.changeset/khaki-ducks-give.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes an edge case with view transitions where some spec-compliant `Content-Type` headers would cause a valid HTML response to be ignored.
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro
index d05973036e14..b78565c86db9 100644
--- a/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro
@@ -6,6 +6,7 @@ import Layout from '../components/Layout.astro';
test
go to 2
go to 3
+ go to 7
go to long page
go to top
go to redirect 2
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/seven.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/seven.astro
new file mode 100644
index 000000000000..625c4a8df661
--- /dev/null
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/seven.astro
@@ -0,0 +1,10 @@
+---
+import Layout from '../components/Layout.astro';
+
+Astro.response.headers.set('Content-Type', 'text/html ; charset=utf-8');
+---
+
+ Page 7
+
+ test content
+
diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js
index 222c9dfdf2aa..a3c88b8b039b 100644
--- a/packages/astro/e2e/view-transitions.test.js
+++ b/packages/astro/e2e/view-transitions.test.js
@@ -97,6 +97,25 @@ test.describe('View Transitions', () => {
expect(loads.length, 'There should only be 1 page load').toEqual(1);
});
+ test('Clicking on a link to a page with non-recommended headers', async ({page, astro}) => {
+ const loads = [];
+ page.addListener('load', (p) => {
+ loads.push(p.title());
+ });
+
+ // Go to page 4
+ await page.goto(astro.resolveUrl('/one'));
+ let p = page.locator('#one');
+ await expect(p, 'should have content').toHaveText('Page 1');
+
+ // Go to page 1
+ await page.click('#click-seven');
+ p = page.locator('#seven');
+ await expect(p, 'should have content').toHaveText('Page 7');
+
+ expect(loads.length, 'There should only be 1 page load').toEqual(1);
+ });
+
test('Moving to a page without ViewTransitions triggers a full page navigation', async ({
page,
astro,
diff --git a/packages/astro/src/transitions/router.ts b/packages/astro/src/transitions/router.ts
index e710c2e1b2e8..6588fd71f960 100644
--- a/packages/astro/src/transitions/router.ts
+++ b/packages/astro/src/transitions/router.ts
@@ -119,8 +119,9 @@ async function fetchHTML(
): Promise {
try {
const res = await fetch(href, init);
+ const contentType = res.headers.get('content-type') ?? '';
// drop potential charset (+ other name/value pairs) as parser needs the mediaType
- const mediaType = res.headers.get('content-type')?.replace(/;.*$/, '');
+ const mediaType = contentType.split(';', 1)[0].trim();
// the DOMParser can handle two types of HTML
if (mediaType !== 'text/html' && mediaType !== 'application/xhtml+xml') {
// everything else (e.g. audio/mp3) will be handled by the browser but not by us