refactor: implement absolute URL resolution for social media meta tags

This commit is contained in:
Raj Nandan Sharma
2026-06-06 12:25:42 +05:30
parent 560c87219b
commit 1750e2a341
8 changed files with 57 additions and 21 deletions
+36
View File
@@ -31,3 +31,39 @@ export default function urlResolve(resolve: ResolveFn, path: string, params?: Re
}
return resolve(path);
}
/**
* Resolves a path to an absolute URL by prefixing the site URL.
* Required for meta tags like og:image and twitter:image that need absolute URLs.
* @param resolve - The resolve function from $app/paths
* @param siteUrl - The site URL (e.g., "https://status.example.com")
* @param path - The route path or absolute URL
* @param params - Optional parameters for dynamic route segments
* @returns An absolute URL, or the resolved relative URL if siteUrl is empty
*
* @example
* ```ts
* absoluteResolve(resolve, "https://status.example.com", "/uploads/preview.png")
* // => "https://status.example.com/uploads/preview.png"
* ```
*/
export function absoluteResolve(
resolve: ResolveFn,
siteUrl: string,
path: string,
params?: Record<string, string>
): string {
// Normalize relative paths like "./assets/..." to "/assets/..." so the
// final URL doesn't contain "/./" segments (crawlers don't normalize these)
const normalizedPath = path.startsWith("./") ? path.slice(1) : path;
const resolved = urlResolve(resolve, normalizedPath, params);
// Already absolute, return as-is
if (resolved.startsWith("http://") || resolved.startsWith("https://")) {
return resolved;
}
if (!siteUrl) {
return resolved;
}
const trimmedSiteUrl = siteUrl.replace(/\/+$/, "");
return trimmedSiteUrl + (resolved.startsWith("/") ? resolved : "/" + resolved);
}
+3 -3
View File
@@ -7,7 +7,7 @@
import IncidentItem from "$lib/components/IncidentItem.svelte";
import MaintenanceItem from "$lib/components/MaintenanceItem.svelte";
import mdToHTML from "$lib/marked.js";
import clientResolver from "$lib/client/resolver.js";
import clientResolver, { absoluteResolve } from "$lib/client/resolver.js";
import { resolve } from "$app/paths";
import { selectedTimezone } from "$lib/stores/timezone";
import { getEndOfDayAtTz } from "$lib/client/datetime";
@@ -136,8 +136,8 @@
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
{#if data.socialPagePreviewImage}
<meta property="og:image" content={clientResolver(resolve, data.socialPagePreviewImage)} />
<meta name="twitter:image" content={clientResolver(resolve, data.socialPagePreviewImage)} />
<meta property="og:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPagePreviewImage)} />
<meta name="twitter:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPagePreviewImage)} />
{/if}
</svelte:head>
+3 -3
View File
@@ -7,7 +7,7 @@
import IncidentItem from "$lib/components/IncidentItem.svelte";
import MaintenanceItem from "$lib/components/MaintenanceItem.svelte";
import mdToHTML from "$lib/marked.js";
import clientResolver from "$lib/client/resolver.js";
import clientResolver, { absoluteResolve } from "$lib/client/resolver.js";
import { resolve } from "$app/paths";
import { selectedTimezone } from "$lib/stores/timezone";
import { getEndOfDayAtTz } from "$lib/client/datetime";
@@ -136,8 +136,8 @@
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
{#if data.socialPagePreviewImage}
<meta property="og:image" content={clientResolver(resolve, data.socialPagePreviewImage)} />
<meta name="twitter:image" content={clientResolver(resolve, data.socialPagePreviewImage)} />
<meta property="og:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPagePreviewImage)} />
<meta name="twitter:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPagePreviewImage)} />
{/if}
</svelte:head>
@@ -13,7 +13,7 @@
import { t } from "$lib/stores/i18n";
import { formatDate } from "$lib/stores/datetime";
import { resolve } from "$app/paths";
import clientResolver from "$lib/client/resolver.js";
import clientResolver, { absoluteResolve } from "$lib/client/resolver.js";
import { format, parse, addMonths, subMonths, getUnixTime, startOfDay, formatDistanceStrict } from "date-fns";
import { page } from "$app/state";
import type { IncidentForMonitorListWithComments, MaintenanceEventsMonitorList } from "$lib/server/types/db";
@@ -165,8 +165,8 @@
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
{#if data.socialPreviewImage}
<meta property="og:image" content={clientResolver(resolve, data.socialPreviewImage)} />
<meta name="twitter:image" content={clientResolver(resolve, data.socialPreviewImage)} />
<meta property="og:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPreviewImage)} />
<meta name="twitter:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPreviewImage)} />
{/if}
</svelte:head>
@@ -13,7 +13,7 @@
import { t } from "$lib/stores/i18n";
import { formatDate } from "$lib/stores/datetime";
import { resolve } from "$app/paths";
import clientResolver from "$lib/client/resolver.js";
import clientResolver, { absoluteResolve } from "$lib/client/resolver.js";
import { format, parse, addMonths, subMonths, getUnixTime, startOfDay, formatDistanceStrict } from "date-fns";
import { page } from "$app/state";
import type { IncidentForMonitorListWithComments, MaintenanceEventsMonitorList } from "$lib/server/types/db";
@@ -165,8 +165,8 @@
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
{#if data.socialPreviewImage}
<meta property="og:image" content={clientResolver(resolve, data.socialPreviewImage)} />
<meta name="twitter:image" content={clientResolver(resolve, data.socialPreviewImage)} />
<meta property="og:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPreviewImage)} />
<meta name="twitter:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPreviewImage)} />
{/if}
</svelte:head>
@@ -12,7 +12,7 @@
import { SveltePurify } from "@humanspeak/svelte-purify";
import { t } from "$lib/stores/i18n";
import { formatDate, formatDuration } from "$lib/stores/datetime";
import clientResolver from "$lib/client/resolver.js";
import clientResolver, { absoluteResolve } from "$lib/client/resolver.js";
import { page } from "$app/state";
let { data } = $props();
@@ -28,8 +28,8 @@
<meta property="og:description" content={data.comments[0].comment} />
{/if}
{#if data.socialPreviewImage}
<meta property="og:image" content={clientResolver(resolve, data.socialPreviewImage)} />
<meta name="twitter:image" content={clientResolver(resolve, data.socialPreviewImage)} />
<meta property="og:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPreviewImage)} />
<meta name="twitter:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPreviewImage)} />
{/if}
</svelte:head>
@@ -15,7 +15,7 @@
import STATUS_ICON from "$lib/icons";
import { t } from "$lib/stores/i18n";
import { formatDate, formatDuration } from "$lib/stores/datetime";
import clientResolver from "$lib/client/resolver.js";
import clientResolver, { absoluteResolve } from "$lib/client/resolver.js";
import { SveltePurify } from "@humanspeak/svelte-purify";
import { page } from "$app/state";
@@ -73,8 +73,8 @@
<meta property="og:description" content={data.maintenance.description} />
{/if}
{#if data.socialPreviewImage}
<meta property="og:image" content={clientResolver(resolve, data.socialPreviewImage)} />
<meta name="twitter:image" content={clientResolver(resolve, data.socialPreviewImage)} />
<meta property="og:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPreviewImage)} />
<meta name="twitter:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPreviewImage)} />
{/if}
</svelte:head>
@@ -6,7 +6,7 @@
import ThemePlus from "$lib/components/ThemePlus.svelte";
import MonitorOverview from "$lib/components/MonitorOverview.svelte";
import ArrowUpRight from "@lucide/svelte/icons/arrow-up-right";
import clientResolver from "$lib/client/resolver.js";
import clientResolver, { absoluteResolve } from "$lib/client/resolver.js";
import { resolve } from "$app/paths";
import trackEvent from "$lib/beacon";
import IncidentItem from "$lib/components/IncidentItem.svelte";
@@ -37,8 +37,8 @@
<meta property="og:description" content={data.monitorDescription} />
{/if}
{#if data.socialPreviewImage}
<meta property="og:image" content={clientResolver(resolve, data.socialPreviewImage)} />
<meta name="twitter:image" content={clientResolver(resolve, data.socialPreviewImage)} />
<meta property="og:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPreviewImage)} />
<meta name="twitter:image" content={absoluteResolve(resolve, data.siteUrl, data.socialPreviewImage)} />
{/if}
</svelte:head>
<div class="flex flex-col gap-3">