diff --git a/src/lib/meta-fetcher.ts b/src/lib/meta-fetcher.ts
index 59d7775..dbe16f8 100644
--- a/src/lib/meta-fetcher.ts
+++ b/src/lib/meta-fetcher.ts
@@ -52,7 +52,7 @@ export function parseMetaTags(html: string, baseUrl: string): MetaTags {
// Extract
const titleMatch = html.match(/]*>(.*?)<\/title>/is);
- if (titleMatch) tags.title = decodeEntities(titleMatch[1].trim());
+ if (titleMatch) tags.title = decodeEntities(titleMatch[1]).replace(/[ \t\n\r\f]+/g, ' ').trim();
// Extract meta tags
for (const tag of findTags(html, 'meta')) {
diff --git a/tests/lib/meta-fetcher.test.ts b/tests/lib/meta-fetcher.test.ts
index 5e90da0..c110c2f 100644
--- a/tests/lib/meta-fetcher.test.ts
+++ b/tests/lib/meta-fetcher.test.ts
@@ -119,6 +119,17 @@ describe('parseMetaTags', () => {
expect(tags.title).toBe('');
});
+ it('collapses multiline indentation, internal tabs, and double spaces in title while preserving non-ASCII whitespace', () => {
+ const tags = parseMetaTags(
+ `
+ Cutting\t\tcold starts in half with Fluid Compute
+ | Acme Blog
+ `,
+ base,
+ );
+ expect(tags.title).toBe('Cutting cold starts in\u00a0half with Fluid Compute | Acme Blog');
+ });
+
it('does not decode uppercase or mixed-case entities that are not valid in HTML', () => {
const tags = parseMetaTags(
'',