at://samuel.bsky.team/com.whtwnd.blog.entry/3l7dnil23ed23

Back to Collection

Record JSON

{
  "$type": "com.whtwnd.blog.entry",
  "content": "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.\n\nWith this component, all you need is the post URI - you can get this from the embed snippet we give you at [embed.bsky.app](https://embed.bsky.app) in the `data-bluesky-uri` attribute.\n\nThis uses Next.js and TailwindCSS, but it should be pretty obvious how to adapt it for your specific project.\n\n```tsx\n\"use client\";\n\nimport { useEffect, useId, useState } from \"react\";\nimport { usePathname } from \"next/navigation\";\n\nconst WEBSITE_URL = \"https://example.com\" // your website goes here\nconst EMBED_URL = \"https://embed.bsky.app\";\n\nexport function BlueskyPostEmbed({ uri }: { uri: string }) {\n  const id = useId();\n  const pathname = usePathname();\n  const [height, setHeight] = useState(0);\n\n  useEffect(() =\u003e {\n    const abortController = new AbortController();\n    const { signal } = abortController;\n    window.addEventListener(\n      \"message\",\n      (event) =\u003e {\n        if (event.origin !== EMBED_URL) {\n          return;\n        }\n\n        const iframeId = (event.data as { id: string }).id;\n        if (id !== iframeId) {\n          return;\n        }\n\n        const internalHeight = (event.data as { height: number }).height;\n        if (internalHeight \u0026\u0026 typeof internalHeight === \"number\") {\n          setHeight(internalHeight);\n        }\n      },\n      { signal },\n    );\n\n    return () =\u003e {\n      abortController.abort();\n    };\n  }, [id]);\n\n  const ref_url = WEBSITE_URL + pathname;\n\n  const searchParams = new URLSearchParams();\n  searchParams.set(\"id\", id);\n  searchParams.set(\"ref_url\", encodeURIComponent(ref_url));\n\n  const src = `${EMBED_URL}/embed/${uri.slice(\"at://\".length)}?${searchParams.toString()}`\n\n  return (\n    \u003cdiv\n      className=\"flex max-w-[600px] w-full bluesky-embed\"\n      data-uri={uri}\n    \u003e\n      \u003ciframe\n        className=\"w-full block border-none flex-grow\"\n        style={{ height }}\n        data-bluesky-uri={uri}\n        src={src}\n        width=\"100%\"\n        frameBorder=\"0\"\n        scrolling=\"no\"\n      /\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\nHere it is in action:\n\n\u003cblockquote class=\"bluesky-embed\" data-bluesky-uri=\"at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.feed.post/3l644ua33f526\" data-bluesky-cid=\"bafyreihj365zpnqw6heer6y7d7xwugkf54cohsxw625hizprxhpnztkbqq\"\u003e\u003cp lang=\"en\"\u003eto write a blog post one must first invent a blogging protocol\u003c/p\u003e\u0026mdash; Samuel (\u003ca href=\"https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep?ref_src=embed\"\u003e@samuel.bsky.team\u003c/a\u003e) \u003ca href=\"https://bsky.app/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep/post/3l644ua33f526?ref_src=embed\"\u003eOctober 9, 2024 at 11:00 PM\u003c/a\u003e\u003c/blockquote\u003e\n\nHope that helps somebody!",
  "createdAt": "2024-10-26T15:38:51.667Z",
  "theme": "github-light",
  "title": "Bluesky post embeds in Next.js",
  "visibility": "public"
}