diff --git a/src/lib/components/leaflet/LeafletBskyPost.svelte b/src/lib/components/leaflet/LeafletBskyPost.svelte
index e7542a8..acab672 100644
--- a/src/lib/components/leaflet/LeafletBskyPost.svelte
+++ b/src/lib/components/leaflet/LeafletBskyPost.svelte
@@ -1,4 +1,6 @@
diff --git a/src/lib/utils/bluesky.ts b/src/lib/utils/bluesky.ts
new file mode 100644
index 0000000..50b7ad1
--- /dev/null
+++ b/src/lib/utils/bluesky.ts
@@ -0,0 +1,30 @@
+const DEFAULT_BLUESKY_CLIENT = "https://bsky.app";
+
+function blueskyClientUrl(clientHost?: string): string {
+ const configuredHost = clientHost?.trim();
+ if (!configuredHost) return DEFAULT_BLUESKY_CLIENT;
+
+ try {
+ const url = new URL(
+ /^https?:\/\//i.test(configuredHost)
+ ? configuredHost
+ : `https://${configuredHost}`,
+ );
+
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
+ return DEFAULT_BLUESKY_CLIENT;
+ }
+
+ return url.href.replace(/\/$/, "");
+ } catch {
+ return DEFAULT_BLUESKY_CLIENT;
+ }
+}
+
+export function blueskyPostUrl(atUri: string, clientHost?: string): string {
+ const parts = atUri.replace(/^at:\/\//, "").split("/");
+ const did = parts[0];
+ const rkey = parts.at(-1);
+
+ return `${blueskyClientUrl(clientHost)}/profile/${did}/post/${rkey}`;
+}