Back

Bluesky post embeds in Next.js

Samuel's profile pictureSamuel · · 2 min read

The way that Bluesky post embeds work doesn't mesh very nicely with React since it swaps out elements in the DOM that React is trying to keep track of, and I was having a lot of trouble getting it work with Next.js due to its fancy router. Luckily, the embed script is very simple, and you can simply reimplement it as a React component. The snippet doesn't do a whole lot—it swaps out the placeholder blockquote for an iframe, then listens to messages that the iframe sends up to the parent frame to set its height. This is because iframe content can't influence the size of the frame, so we have to set it manually.

With this component, all you need is the post URI - you can get this from the embed snippet we give you at embed.bsky.app in the data-bluesky-uri attribute.

This uses Next.js and TailwindCSS, but it should be pretty obvious how to adapt it for your specific project.

"use client";

import { useEffect, useId, useState } from "react";
import { usePathname } from "next/navigation";

const WEBSITE_URL = "https://example.com" // your website goes here
const EMBED_URL = "https://embed.bsky.app";

export function BlueskyPostEmbed({ uri }: { uri: string }) {
const id = useId();
const pathname = usePathname();
const [height, setHeight] = useState(0);

useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
window.addEventListener(
"message",
(event) => {
if (event.origin !== EMBED_URL) {
return;
}

const iframeId = (event.data as { id: string }).id;
if (id !== iframeId) {
return;
}

const internalHeight = (event.data as { height: number }).height;
if (internalHeight && typeof internalHeight === "number") {
setHeight(internalHeight);
}
},
{ signal },
);

return () => {
abortController.abort();
};
}, [id]);

const ref_url = WEBSITE_URL + pathname;

const searchParams = new URLSearchParams();
searchParams.set("id", id);
searchParams.set("ref_url", encodeURIComponent(ref_url));

const src = `${EMBED_URL}/embed/${uri.slice("at://".length)}?${searchParams.toString()}`

return (
<div
className="flex max-w-[600px] w-full bluesky-embed"
data-uri={uri}
>
<iframe
className="w-full block border-none flex-grow"
style={{ height }}
data-bluesky-uri={uri}
src={src}
width="100%"
frameBorder="0"
scrolling="no"
/>
</div>
);
}

Here it is in action:

Hope that helps somebody!