Skip to content

Commit d03e609

Browse files
authored
Merge pull request #4078 from cryst-hq/codex/fix-umami-4062
Fix metadata base URL generation
2 parents 143e220 + 8d2b9f8 commit d03e609

File tree

3 files changed

+99
-6
lines changed

3 files changed

+99
-6
lines changed

src/app/layout.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,14 @@ export default function ({ children }) {
4343
);
4444
}
4545

46-
export const metadata: Metadata = {
47-
title: {
48-
template: '%s | Umami',
49-
default: 'Umami',
50-
},
51-
};
46+
export async function generateMetadata(): Promise<Metadata> {
47+
const headerStore = await headers();
48+
49+
return {
50+
metadataBase: getBaseUrl(headerStore),
51+
title: {
52+
template: '%s | Umami',
53+
default: 'Umami',
54+
},
55+
};
56+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { HOMEPAGE_URL } from '../constants';
2+
import { getBaseUrl } from '../get-base-url';
3+
4+
function createHeaders(entries: Record<string, string>) {
5+
return {
6+
get(name: string) {
7+
return entries[name.toLowerCase()] ?? null;
8+
},
9+
};
10+
}
11+
12+
test('prefers forwarded host and protocol', () => {
13+
const url = getBaseUrl(
14+
createHeaders({
15+
'x-forwarded-host': 'umami.is',
16+
'x-forwarded-proto': 'https',
17+
host: 'localhost:3000',
18+
}),
19+
);
20+
21+
expect(url.toString()).toBe('https://umami.is/');
22+
});
23+
24+
test('falls back to host header', () => {
25+
const url = getBaseUrl(
26+
createHeaders({
27+
host: 'analytics.example.com',
28+
}),
29+
);
30+
31+
expect(url.toString()).toBe('https://analytics.example.com/');
32+
});
33+
34+
test('uses http for localhost hosts', () => {
35+
const url = getBaseUrl(
36+
createHeaders({
37+
host: 'localhost:3000',
38+
}),
39+
);
40+
41+
expect(url.toString()).toBe('http://localhost:3000/');
42+
});
43+
44+
test('falls back to homepage when host is missing', () => {
45+
const url = getBaseUrl(createHeaders({}));
46+
47+
expect(url.toString()).toBe(`${HOMEPAGE_URL}/`);
48+
});

src/lib/get-base-url.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { HOMEPAGE_URL } from './constants';
2+
3+
type HeaderStore = Pick<Headers, 'get'>;
4+
5+
function getFirstHeaderValue(value?: string | null) {
6+
return value?.split(',')[0]?.trim();
7+
}
8+
9+
function getDefaultProtocol(host?: string) {
10+
if (!host) {
11+
return 'https';
12+
}
13+
14+
if (host.startsWith('localhost') || host.startsWith('127.0.0.1') || host.startsWith('[::1]')) {
15+
return 'http';
16+
}
17+
18+
return 'https';
19+
}
20+
21+
export function getBaseUrl(headers?: HeaderStore) {
22+
const host =
23+
getFirstHeaderValue(headers?.get('x-forwarded-host')) ||
24+
getFirstHeaderValue(headers?.get('host'));
25+
26+
if (!host) {
27+
return new URL(HOMEPAGE_URL);
28+
}
29+
30+
const protocol =
31+
getFirstHeaderValue(headers?.get('x-forwarded-proto')) ||
32+
getFirstHeaderValue(headers?.get('x-forwarded-protocol')) ||
33+
getDefaultProtocol(host);
34+
35+
try {
36+
return new URL(`${protocol}://${host}`);
37+
} catch {
38+
return new URL(HOMEPAGE_URL);
39+
}
40+
}

0 commit comments

Comments
 (0)