Contribute to Bluesky Community. Read more
Introduction

Introduction

react-bluesky-embed allows you to embed post threads in your React application when using Next.js, Create React App, Vite, and more. This library does require using the Bluesky API. Post threads can be rendered statically, preventing the need to include an iframe and additional client-side JavaScript.


You can see how it in action in react-bluesky-embed-next.vercel.app/light/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u. Replace the postThread ID in the URL to see other post threads.

This library is fully compatible with React Server Components.

Installation

Install react-bluesky-embed using your package manager of choice:

npm install react-bluesky-embed

Now follow the usage instructions for your framework or builder:

Important: Before going to production, we recommend enabling cache for the Bluesky API as server IPs might get rate limited by Bluesky.

Choosing a theme

Toggling theme manually

The closest theme prop can determine the theme of the postThread. You can set it to light or dark, like so:

<div className="max-w-[672px]">
  <PostThread
    params={{
      did: "did:plc:gru662w3omynujkgwebgeeof",
      rkey: "3lbirib5xnc2u",
    }}
    theme="dark"
    // set the depth to 1+ to show replies
    config={{
      depth: 6,
    }}
    // only show the replies
    hidePost={false}
  />
</div>

Enabling cache for the Bluesky API

Rendering post threads requires making a call to Bluesky’s API. Getting rate limited by that API is very hard but it’s possible if you’re relying only on the endpoint we provide for SWR (react-bluesky-embed.vercel.app/api/postThread/:did-:rkey) as the IPs of the server are making many requests to the syndication API. This also applies to RSC where the API endpoint is not required but the server is still making the request from the same IP.

To prevent this, you can use a db like Redis or Vercel KV to cache the post threads. For example using Vercel KV:

import { Suspense } from "react";
import {
  PostThreadSkeleton,
  EmbeddedPostThread,
  PostThreadNotFound,
} from "react-bluesky-embed";
import { fetchPostThread, PostThread } from "react-bluesky-embed/api";
import { kv } from "@vercel/kv";
 
async function getPostThread(
  params: PostThreadParams,
  config?: PostThreadConfig
): Promise<PostThread | undefined> {
  try {
    const { data, tombstone, notFound } = await fetchPostThread(
      params,
      config
    );
 
    if (data) {
      await kv.set(`postThread:${params}`, data);
      return data;
    } else if (tombstone || notFound) {
      // remove the postThread from the cache if it has been made private by the author (tombstone)
      // or if it no longer exists.
      await kv.del(`postThread:${params}`);
    }
  } catch (error) {
    console.error("fetching the postThread failed with:", error);
  }
 
  const cachedPostThread = await kv.get<PostThread>(`postThread:${params}`);
  return cachedPostThread ?? undefined;
}
 
const PostThreadPage = async ({
  params,
}: {
  params: { PostThreadParams };
}) => {
  try {
    const postThread = await getPostThread(params);
    return postThread ? (
      <EmbeddedPostThread postThread={postThread} />
    ) : (
      <PostThreadNotFound />
    );
  } catch (error) {
    console.error(error);
    return <PostThreadNotFound error={error} />;
  }
};
 
const Page = ({
  params,
}: {
  params: { postThread: PostThreadParams };
}) => (
  <Suspense fallback={<PostThreadSkeleton />}>
    <PostThreadPage params={params.postThread} />
  </Suspense>
);
 
export default Page;

You can see it working at react-bluesky-embed-next.vercel.app/light/vercel-kv/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u (source).

If you’re using Next.js then using unstable_cache works too.