├── .changeset ├── README.md └── config.json ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── .vscode └── settings.json ├── README.md ├── apps ├── create-react-app │ ├── .env │ ├── package.json │ ├── public │ │ └── index.html │ ├── readme.md │ ├── src │ │ ├── app.jsx │ │ ├── app.module.css │ │ ├── base.css │ │ └── index.jsx │ └── vercel.json ├── next-app │ ├── .eslintrc.json │ ├── .vscode │ │ └── settings.json │ ├── app │ │ ├── api │ │ │ └── postThread │ │ │ │ └── [did] │ │ │ │ └── [rkey] │ │ │ │ └── route.ts │ │ ├── layout.tsx │ │ └── light │ │ │ ├── [did] │ │ │ └── [rkey] │ │ │ │ ├── page.tsx │ │ │ │ └── postThread-components.tsx │ │ │ ├── cache │ │ │ └── [did] │ │ │ │ └── [rkey] │ │ │ │ ├── page.tsx │ │ │ │ └── postThread-page.tsx │ │ │ ├── layout.module.css │ │ │ ├── layout.tsx │ │ │ ├── mdx │ │ │ ├── page.tsx │ │ │ └── post.mdx │ │ │ ├── suspense │ │ │ └── [did] │ │ │ │ └── [rkey] │ │ │ │ ├── page.tsx │ │ │ │ └── postThread-page.tsx │ │ │ └── vercel-kv │ │ │ └── [did] │ │ │ └── [rkey] │ │ │ ├── page.tsx │ │ │ └── postThread-page.tsx │ ├── base.css │ ├── components │ │ ├── postThread-page.module.css │ │ └── postThread-page.tsx │ ├── mdx-components.tsx │ ├── mdx.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── pages │ │ ├── _app.js │ │ └── dark │ │ │ ├── [did] │ │ │ └── [rkey] │ │ │ │ └── index.tsx │ │ │ └── swr │ │ │ └── [did] │ │ │ └── [rkey] │ │ │ └── index.tsx │ ├── readme.md │ ├── tsconfig.json │ └── vercel.json ├── site │ ├── README.md │ ├── app │ │ └── api │ │ │ └── postThread │ │ │ └── [did] │ │ │ └── [rkey] │ │ │ └── route.ts │ ├── components │ │ ├── Sample.tsx │ │ ├── counters.module.css │ │ └── counters.tsx │ ├── next.config.mjs │ ├── package.json │ ├── pages │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── _meta.ts │ │ ├── angular.mdx │ │ ├── contributing.mdx │ │ ├── index.mdx │ │ ├── react │ │ │ ├── _meta.ts │ │ │ ├── api-reference.mdx │ │ │ ├── create-react-app.mdx │ │ │ ├── next.mdx │ │ │ └── vite.mdx │ │ ├── solid.mdx │ │ ├── svelte.mdx │ │ └── vue.mdx │ ├── postcss.config.mjs │ ├── public │ │ ├── favicon.ico │ │ └── opengraph-image.png │ ├── styles │ │ └── base.css │ ├── tailwind.config.mjs │ ├── theme.config.tsx │ ├── tsconfig.json │ └── vercel.json └── vite-app │ ├── .gitignore │ ├── api │ └── postThread │ │ └── [postThread].ts │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── public │ └── vite.svg │ ├── readme.md │ ├── src │ ├── base.css │ ├── layout.tsx │ ├── main.tsx │ ├── pages │ │ ├── index.tsx │ │ └── postThread.tsx │ └── vite-env.d.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vercel.json │ └── vite.config.ts ├── license.md ├── package.json ├── packages ├── bluesky-embed-core │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .swcrc │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── src │ │ ├── api │ │ │ ├── fetch-postThread.ts │ │ │ ├── get-postThread.ts │ │ │ ├── index.ts │ │ │ └── types │ │ │ │ ├── index.ts │ │ │ │ └── postThread.ts │ │ ├── index.ts │ │ ├── labels.ts │ │ └── utils.ts │ └── tsconfig.json └── react-bluesky-embed │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .swcrc │ ├── CHANGELOG.md │ ├── global.d.ts │ ├── license.md │ ├── package.json │ ├── postcss.config.cjs │ ├── readme.md │ ├── src │ ├── api │ │ ├── fetch-postThread.ts │ │ ├── get-postThread.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── index.ts │ │ │ └── postThread.ts │ ├── assets │ │ ├── arrowBottom_stroke2_corner0_rounded.svg │ │ ├── bubble_filled_stroke2_corner2_rounded.svg │ │ ├── circleInfo_stroke2_corner0_rounded.svg │ │ ├── heart2_filled_stroke2_corner0_rounded.svg │ │ ├── logo.svg │ │ ├── play_filled_corner2_rounded.svg │ │ ├── repost_stroke2_corner2_rounded.svg │ │ └── starterPack.svg │ ├── bluesky-theme │ │ ├── comments.tsx │ │ ├── components.tsx │ │ ├── container.tsx │ │ ├── embed.tsx │ │ ├── embedded-postThread.tsx │ │ ├── icons.tsx │ │ ├── labels.ts │ │ ├── link.tsx │ │ ├── post.tsx │ │ ├── postThread-not-found.tsx │ │ ├── postThread-skeleton.tsx │ │ ├── theme-container.tsx │ │ └── utils.ts │ ├── hooks.ts │ ├── index.client.ts │ ├── index.ts │ ├── main.css │ ├── main.scss │ ├── postThread.tsx │ ├── swr.tsx │ └── utils.ts │ ├── tailwind.config.js │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["*-app", "site"] 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # Testing 9 | /coverage 10 | 11 | # Next.js 12 | .next 13 | out 14 | next-env.d.ts 15 | 16 | # Production 17 | build 18 | dist 19 | 20 | # Misc 21 | .DS_Store 22 | *.pem 23 | tsconfig.tsbuildinfo 24 | 25 | # Debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | # Local ENV files 31 | .env.local 32 | .env.development.local 33 | .env.test.local 34 | .env.production.local 35 | 36 | # Vercel 37 | .vercel 38 | 39 | # Turborepo 40 | .turbo -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers = true -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | .vercel 4 | public 5 | dist 6 | .vscode 7 | 8 | # Ignore dependency locks 9 | pnpm-lock.yaml 10 | package-lock.json 11 | yarn.lock -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "singleQuote": false, 6 | "semi": true, 7 | "bracketSameLine": false, 8 | "bracketSpacing": true, 9 | "jsxSingleQuote": false, 10 | "quoteProps": "as-needed", 11 | "endOfLine": "lf" 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[mdx]": { 3 | "editor.wordWrap": "on", 4 | } 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Bluesky Embed 2 | 3 | NPM version 4 | License 5 | Join the community 6 | Read the documentation 7 | Star the repository 8 | Fork the repository 9 | 10 | React Bluesky Embed allows you to embed post threads, profiles, and comments in your React application when using Next.js, Create React App, Vite, and more. 11 | 12 | Profiles and comments support coming soon. 13 | 14 | Adapters for Solid, Vue, Angular, and Svelte are coming soon. 15 | 16 | ![Banner](/apps/site/public/opengraph-image.png) 17 | 18 | ## Documentation 19 | 20 | For documentation visit [react-bluesky-embed.vercel.app](https://react-bluesky-embed.vercel.app). 21 | 22 | ## Installation 23 | 24 | ```sh 25 | npm i react-bluesky-embed 26 | ``` 27 | 28 | ## Usage 29 | 30 | ```tsx 31 |
32 | 45 |
46 | ``` 47 | 48 | ## Contributing 49 | 50 | Visit our [contributing docs](https://react-bluesky-embed.vercel.app/contributing). 51 | -------------------------------------------------------------------------------- /apps/create-react-app/.env: -------------------------------------------------------------------------------- 1 | BROWSER = none 2 | PORT = 3002 -------------------------------------------------------------------------------- /apps/create-react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-app", 3 | "private": true, 4 | "license": "MIT", 5 | "repository": "https://github.com/hichemfantar/react-bluesky-embed.git", 6 | "author": "Hichem Fantar (https://bsky.app/profile/opensauced.bsky.social)", 7 | "main": "src/index.js", 8 | "scripts": { 9 | "dev": "react-scripts start", 10 | "build": "react-scripts build" 11 | }, 12 | "dependencies": { 13 | "clsx": "^2.1.1", 14 | "react": "^18.3.1", 15 | "react-bluesky-embed": "workspace:*", 16 | "react-dom": "^18.3.1", 17 | "react-scripts": "5.0.1" 18 | }, 19 | "devDependencies": { 20 | "@babel/runtime": "7.22.6", 21 | "@types/node": "20.10.4", 22 | "postcss-flexbugs-fixes": "^5.0.2" 23 | }, 24 | "browserslist": [ 25 | ">0.2%", 26 | "not dead", 27 | "not ie <= 11", 28 | "not op_mini all" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /apps/create-react-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | React App 19 | 20 | 21 | 22 | 25 |
26 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /apps/create-react-app/readme.md: -------------------------------------------------------------------------------- 1 | # react-bluesky-embed for Create React App 2 | 3 | Follow the instructions in the [official docs](https://react-bluesky-embed.vercel.app/create-react-app) to learn more about `react-bluesky-embed` for Create React App. 4 | -------------------------------------------------------------------------------- /apps/create-react-app/src/app.jsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import { PostThread } from "react-bluesky-embed"; 3 | import styles from "./app.module.css"; 4 | import "./base.css"; 5 | 6 | export default function App() { 7 | return ( 8 |
9 |
10 | 16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/create-react-app/src/app.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | font-family: var(--postThread-font-family); 3 | color: var(--postThread-font-color); 4 | background: var(--postThread-bg-color); 5 | height: 100vh; 6 | overflow: auto; 7 | padding: 2rem 1rem; 8 | } 9 | .main { 10 | display: flex; 11 | justify-content: center; 12 | } 13 | -------------------------------------------------------------------------------- /apps/create-react-app/src/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: inherit; 7 | } 8 | html { 9 | height: 100%; 10 | box-sizing: border-box; 11 | } 12 | body { 13 | position: relative; 14 | min-height: 100%; 15 | margin: 0; 16 | line-height: 1.65; 17 | font-family: -apple-system, BlinkMacSystemFont, sans-serif; 18 | font-weight: 400; 19 | text-rendering: optimizeLegibility; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | scroll-behavior: smooth; 23 | } 24 | html, 25 | body { 26 | background: #fff; 27 | } 28 | -------------------------------------------------------------------------------- /apps/create-react-app/src/index.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import App from "./app"; 4 | 5 | const rootElement = document.getElementById("root"); 6 | const root = createRoot(rootElement); 7 | 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /apps/create-react-app/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand": "pnpm turbo build --filter=create-react-app...", 3 | "ignoreCommand": "pnpm dlx turbo-ignore" 4 | } 5 | -------------------------------------------------------------------------------- /apps/next-app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /apps/next-app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "../../node_modules/.pnpm/typescript@4.9.5/node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /apps/next-app/app/api/postThread/[did]/[rkey]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { getPostThread } from "react-bluesky-embed/api"; 3 | 4 | type RouteSegment = { params: { did: string; rkey: string } }; 5 | 6 | export const fetchCache = "only-cache"; 7 | 8 | export async function GET(_req: NextRequest, { params }: RouteSegment) { 9 | try { 10 | const did = params.did; 11 | const rkey = params.rkey; 12 | 13 | const postThread = await getPostThread({ 14 | did: did, 15 | rkey: rkey, 16 | }); 17 | return NextResponse.json( 18 | { data: postThread ?? null }, 19 | { status: postThread ? 200 : 404 } 20 | ); 21 | } catch (error: any) { 22 | console.error(error); 23 | return NextResponse.json( 24 | { error: error.message ?? "Bad request." }, 25 | { status: 400 } 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/next-app/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, ReactNode } from "react"; 2 | import "../base.css"; 3 | 4 | const RootLayout: FC<{ children: ReactNode }> = ({ children }) => ( 5 | 6 | 7 | {children} 8 | 9 | ); 10 | 11 | export default RootLayout; 12 | -------------------------------------------------------------------------------- /apps/next-app/app/light/[did]/[rkey]/page.tsx: -------------------------------------------------------------------------------- 1 | import { PostThread } from "react-bluesky-embed"; 2 | import { getPostThread } from "react-bluesky-embed/api"; 3 | 4 | type Props = { 5 | params: { did: string; rkey: string }; 6 | }; 7 | 8 | export const revalidate = 1800; 9 | 10 | export async function generateMetadata({ params }: Props) { 11 | const did = params.did; 12 | const rkey = params.rkey; 13 | 14 | const postThread = await getPostThread({ 15 | did: did, 16 | rkey: rkey, 17 | }).catch(() => undefined); 18 | 19 | if (!postThread) return { title: "Next PostThread" }; 20 | 21 | // const username = ` - @${postThread.user.screen_name}`; 22 | // const maxLength = 68 - username.length; 23 | // const text = 24 | // postThread.text.length > maxLength 25 | // ? `${postThread.text.slice(0, maxLength)}…` 26 | // : postThread.text; 27 | 28 | return { title: `` }; 29 | // return { title: `${text}${username}` }; 30 | } 31 | 32 | export default function Page({ params }: Props) { 33 | const did = params.did; 34 | const rkey = params.rkey; 35 | 36 | return ( 37 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /apps/next-app/app/light/[did]/[rkey]/postThread-components.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/alt-text */ 2 | // import Image from 'next/image' 3 | // import type { BlueskyComponents } from 'react-bluesky-embed' 4 | 5 | // export const components: BlueskyComponents = { 6 | // AvatarImg: (props) => , 7 | // MediaImg: (props) => , 8 | // } 9 | -------------------------------------------------------------------------------- /apps/next-app/app/light/cache/[did]/[rkey]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import { PostThreadSkeleton } from "react-bluesky-embed"; 3 | import PostThreadPage from "./postThread-page"; 4 | 5 | export const revalidate = 86400; 6 | 7 | const Page = ({ params }: { params: { did: string; rkey: string } }) => { 8 | const did = params.did; 9 | const rkey = params.rkey; 10 | 11 | return ( 12 | }> 13 | 19 | 20 | ); 21 | }; 22 | 23 | export default Page; 24 | -------------------------------------------------------------------------------- /apps/next-app/app/light/cache/[did]/[rkey]/postThread-page.tsx: -------------------------------------------------------------------------------- 1 | import { unstable_cache } from "next/cache"; 2 | import { 3 | getPostThread as _getPostThread, 4 | PostThreadParams, 5 | } from "react-bluesky-embed/api"; 6 | import { EmbeddedPostThread, PostThreadNotFound } from "react-bluesky-embed"; 7 | 8 | const getPostThread = unstable_cache( 9 | async (params: PostThreadParams) => _getPostThread(params), 10 | ["postThread"], 11 | { revalidate: 3600 * 24 } 12 | ); 13 | 14 | const PostThreadPage = async ({ params }: { params: PostThreadParams }) => { 15 | try { 16 | const postThread = await getPostThread(params); 17 | return postThread ? ( 18 | 19 | ) : ( 20 | 21 | ); 22 | } catch (error) { 23 | console.error(error); 24 | return ; 25 | } 26 | }; 27 | 28 | export default PostThreadPage; 29 | -------------------------------------------------------------------------------- /apps/next-app/app/light/layout.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | font-family: var(--postThread-font-family); 3 | color: var(--postThread-font-color); 4 | background: var(--postThread-bg-color); 5 | height: 100vh; 6 | overflow: auto; 7 | padding: 2rem 1rem; 8 | } 9 | .main { 10 | display: flex; 11 | justify-content: center; 12 | } 13 | .footer { 14 | font-size: 0.875rem; 15 | text-align: center; 16 | } 17 | -------------------------------------------------------------------------------- /apps/next-app/app/light/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, ReactNode } from "react"; 2 | import clsx from "clsx"; 3 | import s from "./layout.module.css"; 4 | 5 | const Layout: FC<{ children: ReactNode }> = ({ children }) => ( 6 |
7 |
8 |
{children}
9 |
10 |

🤯 This post thread was statically generated.

11 |
12 |
13 |
14 | ); 15 | 16 | export default Layout; 17 | -------------------------------------------------------------------------------- /apps/next-app/app/light/mdx/page.tsx: -------------------------------------------------------------------------------- 1 | import PostThread from "./post.mdx"; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /apps/next-app/app/light/mdx/post.mdx: -------------------------------------------------------------------------------- 1 | import { PostThread } from "react-bluesky-embed"; 2 | 3 | 6 | -------------------------------------------------------------------------------- /apps/next-app/app/light/suspense/[did]/[rkey]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import { PostThreadSkeleton } from "react-bluesky-embed"; 3 | import PostThreadPage from "./postThread-page"; 4 | 5 | export const revalidate = 3600; 6 | 7 | const Page = ({ params }: { params: { did: string; rkey: string } }) => { 8 | const did = params.did; 9 | const rkey = params.rkey; 10 | 11 | return ( 12 | }> 13 | 19 | 20 | ); 21 | }; 22 | 23 | export default Page; 24 | -------------------------------------------------------------------------------- /apps/next-app/app/light/suspense/[did]/[rkey]/postThread-page.tsx: -------------------------------------------------------------------------------- 1 | import { getPostThread, PostThreadParams } from "react-bluesky-embed/api"; 2 | import { EmbeddedPostThread, PostThreadNotFound } from "react-bluesky-embed"; 3 | 4 | const PostThreadPage = async ({ params }: { params: PostThreadParams }) => { 5 | const did = params.did; 6 | const rkey = params.rkey; 7 | 8 | try { 9 | const postThread = await getPostThread({ 10 | did, 11 | rkey, 12 | }); 13 | return postThread ? ( 14 | 15 | ) : ( 16 | 17 | ); 18 | } catch (error) { 19 | console.error(error); 20 | return ; 21 | } 22 | }; 23 | 24 | export default PostThreadPage; 25 | -------------------------------------------------------------------------------- /apps/next-app/app/light/vercel-kv/[did]/[rkey]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import { PostThreadSkeleton } from "react-bluesky-embed"; 3 | import PostThreadPage from "./postThread-page"; 4 | 5 | export const revalidate = 86400; 6 | 7 | const Page = ({ params }: { params: { did: string; rkey: string } }) => { 8 | const did = params.did; 9 | const rkey = params.rkey; 10 | 11 | return ( 12 | }> 13 | 19 | 20 | ); 21 | }; 22 | 23 | export default Page; 24 | -------------------------------------------------------------------------------- /apps/next-app/app/light/vercel-kv/[did]/[rkey]/postThread-page.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | fetchPostThread, 3 | PostThread, 4 | PostThreadParams, 5 | } from "react-bluesky-embed/api"; 6 | import { EmbeddedPostThread, PostThreadNotFound } from "react-bluesky-embed"; 7 | import { kv } from "@vercel/kv"; 8 | 9 | async function getPostThread(params: { 10 | did: string; 11 | rkey: string; 12 | }): Promise { 13 | const serializedParams = JSON.stringify(params); 14 | 15 | try { 16 | const { data, notFound } = await fetchPostThread({ params }); 17 | 18 | if (data) { 19 | await kv.set(`postThread:${serializedParams}`, data); 20 | return data; 21 | } else if (notFound) { 22 | // remove the postThread from the cache if it has been made private by the author (tombstone) 23 | // or if it no longer exists. 24 | await kv.del(`postThread:${serializedParams}`); 25 | } 26 | } catch (error) { 27 | console.error("fetching the postThread failed with:", error); 28 | } 29 | 30 | const cachedPostThread = await kv.get( 31 | `postThread:${serializedParams}` 32 | ); 33 | return cachedPostThread ?? undefined; 34 | } 35 | 36 | const PostThreadPage = async ({ params }: { params: PostThreadParams }) => { 37 | try { 38 | const postThread = await getPostThread(params); 39 | return postThread ? ( 40 | 41 | ) : ( 42 | 43 | ); 44 | } catch (error) { 45 | console.error(error); 46 | return ; 47 | } 48 | }; 49 | 50 | export default PostThreadPage; 51 | -------------------------------------------------------------------------------- /apps/next-app/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: inherit; 7 | } 8 | html { 9 | height: 100%; 10 | box-sizing: border-box; 11 | } 12 | body { 13 | position: relative; 14 | min-height: 100%; 15 | margin: 0; 16 | line-height: 1.65; 17 | font-family: -apple-system, BlinkMacSystemFont, sans-serif; 18 | font-weight: 400; 19 | text-rendering: optimizeLegibility; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | scroll-behavior: smooth; 23 | } 24 | html, 25 | body { 26 | background: #fff; 27 | } 28 | -------------------------------------------------------------------------------- /apps/next-app/components/postThread-page.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | font-family: var(--postThread-font-family); 3 | color: var(--postThread-font-color); 4 | background: var(--postThread-bg-color); 5 | height: 100vh; 6 | overflow: auto; 7 | padding: 2rem 1rem; 8 | } 9 | .main { 10 | display: flex; 11 | justify-content: center; 12 | } 13 | .footer { 14 | font-size: 0.875rem; 15 | text-align: center; 16 | } 17 | -------------------------------------------------------------------------------- /apps/next-app/components/postThread-page.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from "react"; 2 | import clsx from "clsx"; 3 | import s from "./postThread-page.module.css"; 4 | 5 | type Props = { children?: ReactNode; footer?: boolean }; 6 | 7 | export const PostThreadPage = ({ children, footer }: Props) => ( 8 |
9 |
10 |
{children}
11 | {footer && ( 12 |
13 |

🤯 This post thread was statically generated.

14 |
15 | )} 16 |
17 |
18 | ); 19 | -------------------------------------------------------------------------------- /apps/next-app/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | export const useMDXComponents = (components: any) => components; 2 | -------------------------------------------------------------------------------- /apps/next-app/mdx.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.mdx" { 2 | let MDXComponent: (props) => JSX.Element; 3 | export default MDXComponent; 4 | } 5 | -------------------------------------------------------------------------------- /apps/next-app/next.config.mjs: -------------------------------------------------------------------------------- 1 | import mdx from "@next/mdx"; 2 | 3 | const withMDX = mdx(); 4 | 5 | /** @type {import('next').NextConfig} */ 6 | const nextConfig = { 7 | pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"], 8 | images: { 9 | remotePatterns: [{ protocol: "https", hostname: "cdn.bsky.app" }], 10 | }, 11 | experimental: { 12 | mdxRs: true, 13 | }, 14 | }; 15 | 16 | export default withMDX(nextConfig); 17 | -------------------------------------------------------------------------------- /apps/next-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-app", 3 | "private": true, 4 | "license": "MIT", 5 | "repository": "https://github.com/hichemfantar/react-bluesky-embed.git", 6 | "author": "Hichem Fantar (https://bsky.app/profile/opensauced.bsky.social)", 7 | "scripts": { 8 | "dev": "next dev -p 3001", 9 | "build": "next build", 10 | "start": "next start -p 3001", 11 | "lint": "next lint", 12 | "clean": "rm -rf .next && rm -rf .turbo" 13 | }, 14 | "dependencies": { 15 | "@next/mdx": "^14.2.18", 16 | "@vercel/kv": "^3.0.0", 17 | "clsx": "^2.1.1", 18 | "next": "^14.2.18", 19 | "react": "^18.3.1", 20 | "react-bluesky-embed": "workspace:*", 21 | "react-dom": "^18.3.1" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "20.10.4", 25 | "@types/react": "^18.3.12", 26 | "eslint": "^8.57.1", 27 | "eslint-config-next": "^14.2.19", 28 | "typescript": "^5.7.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /apps/next-app/pages/_app.js: -------------------------------------------------------------------------------- 1 | import "../base.css"; 2 | 3 | export default function App({ Component, pageProps }) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /apps/next-app/pages/dark/[did]/[rkey]/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { EmbeddedPostThread, PostThreadSkeleton } from "react-bluesky-embed"; 3 | import { getPostThread, type PostThread } from "react-bluesky-embed/api"; 4 | import { PostThreadPage } from "../../../../components/postThread-page"; 5 | 6 | export async function getStaticProps({ 7 | params, 8 | }: { 9 | params: { did: string; rkey: string }; 10 | }) { 11 | try { 12 | const did = params.did; 13 | const rkey = params.rkey; 14 | const postThread = await getPostThread({ 15 | did, 16 | rkey, 17 | }); 18 | return postThread ? { props: { postThread } } : { notFound: true }; 19 | } catch (error) { 20 | console.error(error); 21 | return { notFound: true }; 22 | } 23 | } 24 | 25 | export async function getStaticPaths() { 26 | return { paths: [], fallback: true }; 27 | } 28 | 29 | export default function Page({ postThread }: { postThread: PostThread }) { 30 | const { isFallback } = useRouter(); 31 | 32 | return ( 33 | 34 | {isFallback ? ( 35 | 36 | ) : ( 37 | 38 | )} 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /apps/next-app/pages/dark/swr/[did]/[rkey]/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { PostThread } from "react-bluesky-embed"; 3 | import { PostThreadPage } from "../../../../../components/postThread-page"; 4 | 5 | export default function Page() { 6 | const router = useRouter(); 7 | 8 | const did = router.query.did; 9 | const rkey = router.query.rkey; 10 | 11 | // https://github.com/vercel/next.js/discussions/11484 12 | if (!router.isReady) { 13 | return null; 14 | } 15 | 16 | return ( 17 | 18 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/next-app/readme.md: -------------------------------------------------------------------------------- 1 | # react-bluesky-embed for Next.js 2 | 3 | Follow the instructions in the [official docs](https://react-bluesky-embed.vercel.app/react/next) to learn more about `react-bluesky-embed` for Next.js. 4 | -------------------------------------------------------------------------------- /apps/next-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": false, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "incremental": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "strictNullChecks": false 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/next-app/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand": "pnpm turbo build --filter=next-app...", 3 | "ignoreCommand": "pnpm dlx turbo-ignore" 4 | } 5 | -------------------------------------------------------------------------------- /apps/site/README.md: -------------------------------------------------------------------------------- 1 | # react-bluesky-embed site 2 | 3 | This is documentation site app for `react-bluesky-embed`. It uses [Nextra](https://nextra.site). 4 | 5 | ## Running the app locally 6 | 7 | Clone this repository and run the following command: 8 | 9 | ```bash 10 | pnpm install && pnpm dev --filter=site 11 | ``` 12 | 13 | The app will be up at running at . 14 | -------------------------------------------------------------------------------- /apps/site/app/api/postThread/[did]/[rkey]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { getPostThread } from "react-bluesky-embed/api"; 3 | import cors from "edge-cors"; 4 | import { type NextRequest } from "next/server"; 5 | 6 | type RouteSegment = { params: { did: string; rkey: string } }; 7 | 8 | export const fetchCache = "only-cache"; 9 | 10 | // TODO: refactor all [postthread] routes with nested routes [did]->[rkey] 11 | 12 | export async function GET(req: NextRequest, { params }: RouteSegment) { 13 | try { 14 | const did = params.did; 15 | const rkey = params.rkey; 16 | 17 | const postThread = await getPostThread({ 18 | did: did, 19 | rkey: rkey, 20 | }); 21 | 22 | return cors( 23 | req, 24 | NextResponse.json( 25 | { data: postThread ?? null }, 26 | { status: postThread ? 200 : 404 } 27 | ) 28 | ); 29 | } catch (error: any) { 30 | console.error(error); 31 | return cors( 32 | req, 33 | NextResponse.json( 34 | { error: error.message ?? "Bad request." }, 35 | { status: 400 } 36 | ) 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/site/components/Sample.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "nextra-theme-docs"; 2 | import React, { useEffect, useState } from "react"; 3 | import { PostThread, Theme } from "react-bluesky-embed"; 4 | 5 | export function Sample() { 6 | const { resolvedTheme } = useTheme(); 7 | 8 | return ( 9 |
10 | 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /apps/site/components/counters.module.css: -------------------------------------------------------------------------------- 1 | .counter { 2 | border: 1px solid #ccc; 3 | border-radius: 5px; 4 | padding: 2px 6px; 5 | margin: 12px 0 0; 6 | } 7 | -------------------------------------------------------------------------------- /apps/site/components/counters.tsx: -------------------------------------------------------------------------------- 1 | // Example from https://beta.reactjs.org/learn 2 | 3 | import { useState } from "react"; 4 | import styles from "./counters.module.css"; 5 | 6 | function MyButton() { 7 | const [count, setCount] = useState(0); 8 | 9 | function handleClick() { 10 | setCount(count + 1); 11 | } 12 | 13 | return ( 14 |
15 | 18 |
19 | ); 20 | } 21 | 22 | export default function MyApp() { 23 | return ; 24 | } 25 | -------------------------------------------------------------------------------- /apps/site/next.config.mjs: -------------------------------------------------------------------------------- 1 | import nextra from "nextra"; 2 | 3 | const withNextra = nextra({ 4 | // ... your Nextra config 5 | theme: "nextra-theme-docs", 6 | themeConfig: "./theme.config.tsx", 7 | defaultShowCopyCode: true, 8 | }); 9 | 10 | export default withNextra({ 11 | // ... your Next.js config 12 | }); 13 | -------------------------------------------------------------------------------- /apps/site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "site", 3 | "private": true, 4 | "license": "MIT", 5 | "repository": "https://github.com/hichemfantar/react-bluesky-embed.git", 6 | "author": "Hichem Fantar (https://bsky.app/profile/opensauced.bsky.social)", 7 | "description": "Official site and documentation for react-bluesky-embed", 8 | "type": "module", 9 | "scripts": { 10 | "dev": "next dev", 11 | "build": "next build", 12 | "start": "next start" 13 | }, 14 | "dependencies": { 15 | "edge-cors": "^0.2.1", 16 | "next": "^14.2.18", 17 | "nextra": "^3.2.4", 18 | "nextra-theme-docs": "^3.2.4", 19 | "react": "^18.3.1", 20 | "react-bluesky-embed": "workspace:*", 21 | "react-dom": "^18.3.1" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "20.10.5", 25 | "@types/react": "^18.3.12", 26 | "autoprefixer": "^10.4.20", 27 | "postcss": "^8.4.49", 28 | "tailwindcss": "^3.4.15", 29 | "typescript": "^5.7.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /apps/site/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from "next/app"; 2 | import "../styles/base.css"; 3 | 4 | export default function App({ Component, pageProps }: AppProps) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /apps/site/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/site/pages/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | index: "Introduction", 3 | "-- Usage": { 4 | type: "separator", 5 | title: "Usage", 6 | }, 7 | react: "React", 8 | vue: "Vue", 9 | angular: "Angular", 10 | solid: "Solid", 11 | svelte: "Svelte", 12 | "-- More": { 13 | type: "separator", 14 | title: "More", 15 | }, 16 | contributing: "", 17 | "next.js-link": { 18 | title: "Next.js Docs ↗", 19 | href: "https://nextjs.org?utm_source=react-bluesky-embed.site&utm_medium=referral&utm_campaign=sidebar", 20 | newWindow: true, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /apps/site/pages/angular.mdx: -------------------------------------------------------------------------------- 1 | # Coming Soon -------------------------------------------------------------------------------- /apps/site/pages/contributing.mdx: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | To contribute, clone the [`react-bluesky-embed` repository](https://github.com/hichemfantar/react-bluesky-embed) and run the [Next.js test app](/react/next#running-the-test-app) to start an app locally that uses the [`react-bluesky-embed` package](https://github.com/hichemfantar/react-bluesky-embed/blob/main/packages/react-bluesky-embed). Any changes you make to the package will be reflected in the test app. 4 | 5 | Once you're done making changes, [submit a pull request](https://github.com/hichemfantar/react-bluesky-embed/compare). 6 | 7 | It's recommended to [open an issue](https://github.com/hichemfantar/react-bluesky-embed/issues/new) first before making any major changes to the package so that we can discuss the changes before you start working on them. 8 | -------------------------------------------------------------------------------- /apps/site/pages/index.mdx: -------------------------------------------------------------------------------- 1 | import { Sample } from "../components/Sample"; 2 | 3 | # Introduction 4 | 5 | `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. 6 | 7 |
8 | 9 | 10 | You can see how it in action in [react-bluesky-embed-next.vercel.app/light/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u](https://react-bluesky-embed-next.vercel.app/light/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u). Replace the postThread ID in the URL to see other post threads. 11 | 12 | This library is fully compatible with [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components). 13 | 14 | ## Installation 15 | 16 | Install `react-bluesky-embed` using your package manager of choice: 17 | 18 | ```bash 19 | npm install react-bluesky-embed 20 | ``` 21 | 22 | Now follow the usage instructions for your framework or builder: 23 | 24 | - [Next.js](/react/next) 25 | - [Vite](/react/vite) 26 | - [Create React App](/react/create-react-app) 27 | 28 | > **Important**: Before going to production, we recommend [enabling cache for the Bluesky API](#enabling-cache-for-the-bluesky-api) as server IPs might get rate limited by Bluesky. 29 | 30 | ## Choosing a theme 31 | 32 | ### Toggling theme manually 33 | 34 | The closest `theme` prop can determine the theme of the postThread. You can set it to `light` or `dark`, like so: 35 | 36 | ```tsx 37 |
38 | 51 |
52 | ``` 53 | 54 | ## Enabling cache for the Bluesky API 55 | 56 | 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. 57 | 58 | To prevent this, you can use a db like Redis or [Vercel KV](https://vercel.com/docs/storage/vercel-kv) to cache the post threads. For example using [Vercel KV](https://vercel.com/docs/storage/vercel-kv): 59 | 60 | ```tsx 61 | import { Suspense } from "react"; 62 | import { 63 | PostThreadSkeleton, 64 | EmbeddedPostThread, 65 | PostThreadNotFound, 66 | } from "react-bluesky-embed"; 67 | import { fetchPostThread, PostThread } from "react-bluesky-embed/api"; 68 | import { kv } from "@vercel/kv"; 69 | 70 | async function getPostThread( 71 | params: PostThreadParams, 72 | config?: PostThreadConfig 73 | ): Promise { 74 | try { 75 | const { data, tombstone, notFound } = await fetchPostThread( 76 | params, 77 | config 78 | ); 79 | 80 | if (data) { 81 | await kv.set(`postThread:${params}`, data); 82 | return data; 83 | } else if (tombstone || notFound) { 84 | // remove the postThread from the cache if it has been made private by the author (tombstone) 85 | // or if it no longer exists. 86 | await kv.del(`postThread:${params}`); 87 | } 88 | } catch (error) { 89 | console.error("fetching the postThread failed with:", error); 90 | } 91 | 92 | const cachedPostThread = await kv.get(`postThread:${params}`); 93 | return cachedPostThread ?? undefined; 94 | } 95 | 96 | const PostThreadPage = async ({ 97 | params, 98 | }: { 99 | params: { PostThreadParams }; 100 | }) => { 101 | try { 102 | const postThread = await getPostThread(params); 103 | return postThread ? ( 104 | 105 | ) : ( 106 | 107 | ); 108 | } catch (error) { 109 | console.error(error); 110 | return ; 111 | } 112 | }; 113 | 114 | const Page = ({ 115 | params, 116 | }: { 117 | params: { postThread: PostThreadParams }; 118 | }) => ( 119 | }> 120 | 121 | 122 | ); 123 | 124 | export default Page; 125 | ``` 126 | 127 | You can see it working at [react-bluesky-embed-next.vercel.app/light/vercel-kv/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u](https://react-bluesky-embed-next.vercel.app/light/vercel-kv/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u) ([source](https://github.com/vercel/react-bluesky-embed/blob/main/apps/next-app/app/light/vercel-kv/%5BpostThread%5D/page.tsx)). 128 | 129 | If you're using Next.js then using [`unstable_cache`](/react/next#enabling-cache) works too. 130 | -------------------------------------------------------------------------------- /apps/site/pages/react/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | next: "Next.js", 3 | vite: "Vite", 4 | "create-react-app": "CRA", 5 | "api-reference": "", 6 | }; 7 | -------------------------------------------------------------------------------- /apps/site/pages/react/api-reference.mdx: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | This is the reference for the utility functions that `react-bluesky-embed` provides for [building your own postThread components](/custom-theme) or simply fetching a postThread. Navigate to the docs for the [Bluesky theme](/bluesky-theme) if you want to render the existing PostThread components instead. 4 | 5 | ## `getPostThread` 6 | 7 | ```tsx 8 | import { getPostThread, type PostThread } from "react-bluesky-embed/api"; 9 | 10 | function getPostThread( 11 | params: PostThreadParams, 12 | config?: PostThreadConfig 13 | ): Promise; 14 | ``` 15 | 16 | Fetches and returns a [`PostThread`](https://github.com/hichemfantar/react-bluesky-embed/blob/main/packages/react-bluesky-embed/src/api/types/postThread.ts). It accepts the following params: 17 | 18 | - **params** - `{ did: string; rkey: string }`: the postThread ID. For example in `at://did:plc:zl7kgfro2rx3pavbslhhdhuy/app.bsky.feed.post/3lblfjf4evs2v` the DID is `did:plc:zl7kgfro2rx3pavbslhhdhuy` and the Rkey is `3lblfjf4evs2v`. 19 | - **config** - `PostThreadConfig` (Optional): options to pass to [`getPostThread`](https://docs.bsky.app/docs/api/app-bsky-feed-get-post-thread#request). 20 | 21 | If a postThread is not found it returns `undefined`. 22 | 23 | ## `fetchPostThread` 24 | 25 | ```tsx 26 | function fetchPostThread( 27 | params: PostThreadParams, 28 | config?: PostThreadConfig 29 | ): Promise<{ 30 | data?: PostThread | undefined; 31 | tombstone?: true | undefined; 32 | notFound?: true | undefined; 33 | }>; 34 | ``` 35 | 36 | Fetches and returns a [`PostThread`](https://github.com/hichemfantar/react-bluesky-embed/blob/main/packages/react-bluesky-embed/src/api/types/postThread.ts) just like [`getPostThread`](#getpostthread), but it also returns additional information about the postThread: 37 | 38 | - **data** - `PostThread` (Optional): The postThread data. 39 | - **tombstone** - `true` (Optional): Indicates if the postThread has been made private. 40 | - **notFound** - `true` (Optional): Indicates if the postThread was not found. 41 | 42 | ## `enrichPostThread` 43 | 44 | ```tsx 45 | import { enrichPostThread, type EnrichedPostThread } from "react-bluesky-embed"; 46 | 47 | const enrichPostThread: (postThread: PostThread) => EnrichedPostThread; 48 | ``` 49 | 50 | Enriches a [`PostThread`](https://github.com/hichemfantar/react-bluesky-embed/blob/main/packages/react-bluesky-embed/src/api/types/postThread.ts) as returned by [`getPostThread`](#getpostthread) with additional data. This is useful to more easily build custom postThread components. 51 | 52 | It returns an [`EnrichedPostThread`](https://github.com/hichemfantar/react-bluesky-embed/blob/main/packages/react-bluesky-embed/src/utils.ts). 53 | 54 | ## `usePostThread` 55 | 56 | > If your app supports React Server Components, use [`getPostThread`](#getpostthread) instead. 57 | 58 | ```tsx 59 | import { usePostThread } from "react-bluesky-embed"; 60 | 61 | const usePostThread: ( 62 | params?: PostThreadParams, 63 | config?: PostThreadConfig 64 | ) => { 65 | isLoading: boolean; 66 | data: PostThread | null | undefined; 67 | error: any; 68 | }; 69 | ``` 70 | 71 | SWR hook for fetching a postThread in the browser. It accepts the following parameters: 72 | 73 | - **params** - `{ did: string; rkey: string }`: the postThread ID. For example in `at://did:plc:zl7kgfro2rx3pavbslhhdhuy/app.bsky.feed.post/3lblfjf4evs2v` the DID is `did:plc:zl7kgfro2rx3pavbslhhdhuy` and the Rkey is `3lblfjf4evs2v`. 74 | - **config** - `PostThreadConfig` (Optional): [`getPostThread`](https://docs.bsky.app/docs/api/app-bsky-feed-get-post-thread#request). Try to pass down a reference to the same object to avoid unnecessary re-renders. 75 | 76 | We highly recommend adding your own API endpoint in `apiUrl` for production: 77 | 78 | ```ts 79 | const postThread = usePostThread(params); 80 | ``` 81 | 82 | It's likely you'll never use this hook directly, and `apiUrl` is passed as a prop to a component instead: 83 | 84 | ```tsx 85 | 86 | ``` 87 | -------------------------------------------------------------------------------- /apps/site/pages/react/create-react-app.mdx: -------------------------------------------------------------------------------- 1 | # Create React App 2 | 3 | ## Installation 4 | 5 | Follow the [installation docs in the Introduction](/#installation). 6 | 7 | ## Usage 8 | 9 | In any component, import `PostThread` from `react-bluesky-embed` and use it like so: 10 | 11 | ```tsx 12 | import { PostThread } from "react-bluesky-embed"; 13 | 14 | export default function App() { 15 | return ( 16 | 22 | ); 23 | } 24 | ``` 25 | 26 | You can learn more about `PostThread` in the [Bluesky theme docs](/bluesky-theme). 27 | 28 | ## Running the test app 29 | 30 | Clone the [`react-bluesky-embed`](https://github.com/hichemfantar/react-bluesky-embed) repository and then run the following command: 31 | 32 | ```bash 33 | pnpm install && pnpm dev --filter=create-react-app... 34 | ``` 35 | 36 | The app will be up and running at (localhost:3002)[http://localhost:3002] for the [CRA example](https://github.com/hichemfantar/react-bluesky-embed/tree/main/apps/create-react-app). 37 | 38 | The source code for `react-bluesky-embed` is imported from [packages/react-bluesky-embed](https://github.com/hichemfantar/react-bluesky-embed/tree/main/packages/react-bluesky-embed) and any changes you make to it will be reflected in the app immediately. 39 | -------------------------------------------------------------------------------- /apps/site/pages/react/next.mdx: -------------------------------------------------------------------------------- 1 | # Next.js 2 | 3 | ## Installation 4 | 5 | > Next.js 13.2.1 or higher is required in order to use `react-bluesky-embed`. 6 | 7 | Follow the [installation docs in the Introduction](/#installation). 8 | 9 | ## Usage 10 | 11 | In any component, import `PostThread` from `react-bluesky-embed` and use it like so: 12 | 13 | ```tsx 14 | import { PostThread } from "react-bluesky-embed"; 15 | 16 | export default function Page() { 17 | return ( 18 | 24 | ); 25 | } 26 | ``` 27 | 28 | `PostThread` works differently depending on where it's used. If it's used in the App Router it will fetch the postThread in the server. If it's used in the pages directory it will fetch the postThread in the client with [SWR](https://swr.vercel.app/). 29 | 30 | You can learn more about `PostThread` in the [Bluesky theme docs](/bluesky-theme). And you can learn more about the usage in [Running the test app](#running-the-test-app). 31 | 32 | ### Troubleshooting 33 | 34 | If you see an error saying that CSS can't be imported from `node_modules` in the `pages` directory. Add the following config to `next.config.js`: 35 | 36 | ```js 37 | transpilePackages: ["react-bluesky-embed"]; 38 | ``` 39 | 40 | The error won't happen if the App Router is enabled, where [Next.js supports CSS imports from `node_modules`](https://github.com/vercel/next.js/discussions/27953#discussioncomment-3978605). 41 | 42 | ### Enabling cache 43 | 44 | It's recommended to enable cache for the Bluesky API if you intend to go to production. This is how you can do it with [`unstable_cache`](https://nextjs.org/docs/app/api-reference/functions/unstable_cache): 45 | 46 | ```tsx 47 | import { Suspense } from "react"; 48 | import { unstable_cache } from "next/cache"; 49 | import { 50 | PostThreadSkeleton, 51 | EmbeddedPostThread, 52 | PostThreadNotFound, 53 | } from "react-bluesky-embed"; 54 | import { getPostThread as _getPostThread } from "react-bluesky-embed/api"; 55 | 56 | const getPostThread = unstable_cache( 57 | async (params: PostThreadParams) => _getPostThread(params), 58 | ["postThread"], 59 | { revalidate: 3600 * 24 } 60 | ); 61 | 62 | const PostThreadPage = async ({ 63 | params, 64 | }: { 65 | params: PostThreadParams; 66 | }) => { 67 | try { 68 | const postThread = await getPostThread(params); 69 | return postThread ? ( 70 | 71 | ) : ( 72 | 73 | ); 74 | } catch (error) { 75 | console.error(error); 76 | return ; 77 | } 78 | }; 79 | 80 | const Page = ({ 81 | params, 82 | }: { 83 | params: { postThread: PostThreadParams }; 84 | }) => ( 85 | }> 86 | 87 | 88 | ); 89 | 90 | export default Page; 91 | ``` 92 | 93 | This can prevent getting your server IPs rate limited if they are making too many requests to the Bluesky API. 94 | 95 | ## Advanced usage 96 | 97 | ### Manual data fetching 98 | 99 | You can use the [`getPostThread`](/api-reference#getpostthread) function from `react-bluesky-embed/api` to fetch the postThread manually. This is useful for SSG pages and for other [Next.js data fetching methods](https://nextjs.org/docs/basic-features/data-fetching/overview) in the `pages` directory. 100 | 101 | For example, using `getStaticProps` in `pages/[postThread].tsx` to fetch the postThread and send it as props to the page component: 102 | 103 | ```tsx 104 | import { useRouter } from "next/router"; 105 | import { getPostThread, type PostThread } from "react-bluesky-embed/api"; 106 | import { EmbeddedPostThread, PostThreadSkeleton } from "react-bluesky-embed"; 107 | 108 | export async function getStaticProps({ 109 | params, 110 | }: { 111 | params: { postThread: string }; 112 | }) { 113 | const postThreadId = params.postThread; 114 | 115 | try { 116 | const postThread = await getPostThread(postThreadId); 117 | return postThread ? { props: { postThread } } : { notFound: true }; 118 | } catch (error) { 119 | return { notFound: true }; 120 | } 121 | } 122 | 123 | export async function getStaticPaths() { 124 | return { paths: [], fallback: true }; 125 | } 126 | 127 | export default function Page({ postThread }: { postThread: PostThread }) { 128 | const { isFallback } = useRouter(); 129 | return isFallback ? ( 130 | 131 | ) : ( 132 | 133 | ); 134 | } 135 | ``` 136 | 137 | ### Adding `next/image` 138 | 139 | Add the domain URLs from Bluesky to [`images.remotePatterns`](https://nextjs.org/docs/api-reference/next/image#remote-patterns) in `next.config.js`: 140 | 141 | ```js 142 | /** @type {import('next').NextConfig} */ 143 | const nextConfig = { 144 | images: { 145 | remotePatterns: [{ protocol: "https", hostname: "cdn.bsky.app" }], 146 | }, 147 | }; 148 | ``` 149 | 150 | In `postThread-components.tsx` or elsewhere, import the `Image` component from `next/image` and use it to define custom image components for the postThread: 151 | 152 | ```tsx 153 | import Image from "next/image"; 154 | import type { BlueskyComponents } from "react-bluesky-embed"; 155 | 156 | export const components: BlueskyComponents = { 157 | AvatarImg: (props) => , 158 | MediaImg: (props) => , 159 | }; 160 | ``` 161 | 162 | Then pass the `components` prop to `PostThread`: 163 | 164 | ```tsx 165 | import { PostThread } from "react-bluesky-embed"; 166 | import { components } from "./postThread-components"; 167 | 168 | export default function Page() { 169 | return ( 170 | 177 | ); 178 | } 179 | ``` 180 | 181 | ## Running the test app 182 | 183 | Clone the [`react-bluesky-embed`](https://github.com/hichemfantar/react-bluesky-embed) repository and then run the following command: 184 | 185 | ```bash 186 | pnpm install && pnpm dev --filter=next-app... 187 | ``` 188 | 189 | The app will be up and running at http://localhost:3001 for the [Next.js app example](https://github.com/hichemfantar/react-bluesky-embed/tree/main/apps/next-app). 190 | 191 | The app shows the usage of `react-bluesky-embed` in different scenarios: 192 | 193 | - [localhost:3001/light/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u](http://localhost:3001/light/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u) renders the postThread in the app router. 194 | - [localhost:3001/dark/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u](http://localhost:3001/dark/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u) renders the postThread using SSG in the pages directory. 195 | - [localhost:3001/light/mdx](http://localhost:3001/light/mdx) rendes the postThread in MDX (with the experimental `mdxRs` config enabled). 196 | - [localhost:3001/light/suspense/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u](http://localhost:3001/light/suspense/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u) renders the postThread with a custom `Suspense` wrapper. 197 | - [localhost:3001/dark/swr/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u](http://localhost:3001/dark/swr/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u) uses `apiUrl` to change the API endpoint from which the postThread is fetched in SWR mode. 198 | - [localhost:3001/light/cache/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u](http://localhost:3001/light/suspense/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u) renders the postThread while caching the postThread data with [`unstable_cache`](https://nextjs.org/docs/app/api-reference/functions/unstable_cache). 199 | - [localhost:3001/light/vercel-kv/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u](http://localhost:3001/light/suspense/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u) renders the postThread while caching the postThread data with [Vercel KV](https://vercel.com/docs/storage/vercel-kv). 200 | 201 | The source code for `react-bluesky-embed` is imported from [packages/react-bluesky-embed](https://github.com/hichemfantar/react-bluesky-embed/tree/main/packages/react-bluesky-embed) and any changes you make to it will be reflected in the app immediately. 202 | -------------------------------------------------------------------------------- /apps/site/pages/react/vite.mdx: -------------------------------------------------------------------------------- 1 | # Vite 2 | 3 | ## Installation 4 | 5 | Follow the [installation docs in the Introduction](/#installation). 6 | 7 | ## Usage 8 | 9 | In any component, import `PostThread` from `react-bluesky-embed` and use it like so: 10 | 11 | ```tsx 12 | import { PostThread } from "react-bluesky-embed"; 13 | 14 | export const IndexPage = () => ( 15 | 21 | ); 22 | ``` 23 | 24 | You can learn more about `PostThread` in the [Bluesky theme docs](/bluesky-theme). 25 | 26 | ## Running the test app 27 | 28 | Clone the [`react-bluesky-embed`](https://github.com/hichemfantar/react-bluesky-embed) repository and then run the following command: 29 | 30 | ```bash 31 | pnpm install && pnpm dev --filter=vite-app... 32 | ``` 33 | 34 | The app will be up and running at http://localhost:5173 for the [Vite app example](https://github.com/hichemfantar/react-bluesky-embed/tree/main/apps/vite-app). 35 | 36 | The app shows the usage of `react-bluesky-embed` in different scenarios: 37 | 38 | - [localhost:5173/](http://localhost:5173) renders a single postThread. 39 | - [localhost:5173/postThread/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u](http://localhost:5173/postThread/did:plc:gru662w3omynujkgwebgeeof/3lbirib5xnc2u) renders dynamic post threads with SWR. `PostThread` already uses SWR and this page shows how to implement it manually. 40 | 41 | The source code for `react-bluesky-embed` is imported from [packages/react-bluesky-embed](https://github.com/hichemfantar/react-bluesky-embed/tree/main/packages/react-bluesky-embed) and any changes you make to it will be reflected in the app immediately. 42 | -------------------------------------------------------------------------------- /apps/site/pages/solid.mdx: -------------------------------------------------------------------------------- 1 | # Coming Soon -------------------------------------------------------------------------------- /apps/site/pages/svelte.mdx: -------------------------------------------------------------------------------- 1 | # Coming Soon -------------------------------------------------------------------------------- /apps/site/pages/vue.mdx: -------------------------------------------------------------------------------- 1 | # Coming Soon -------------------------------------------------------------------------------- /apps/site/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | export default { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /apps/site/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hichemfantar/react-bluesky-embed/cd77117894154ab2b911aa449aef9738e966b043/apps/site/public/favicon.ico -------------------------------------------------------------------------------- /apps/site/public/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hichemfantar/react-bluesky-embed/cd77117894154ab2b911aa449aef9738e966b043/apps/site/public/opengraph-image.png -------------------------------------------------------------------------------- /apps/site/styles/base.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /apps/site/tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./pages/**/*.{js,ts,jsx,tsx,md,mdx}", 5 | "./components/**/*.{js,ts,jsx,tsx,md,mdx}", 6 | "./theme.config.tsx", 7 | ], 8 | theme: { 9 | extend: {}, 10 | }, 11 | plugins: [], 12 | darkMode: "class", 13 | }; 14 | -------------------------------------------------------------------------------- /apps/site/theme.config.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { DocsThemeConfig, Link, useConfig } from "nextra-theme-docs"; 3 | 4 | const projectName = "React Bluesky Embed"; 5 | 6 | const config: DocsThemeConfig = { 7 | logo: ( 8 |
9 | 14 | 18 | 19 | {projectName} 20 |
21 | ), 22 | head: function useHead() { 23 | const { title } = useConfig(); 24 | 25 | return ( 26 | <> 27 | {title + " - " + projectName} 28 | 29 | 30 | 31 | 32 | 36 | 40 | 41 | 42 | 43 | 44 | 48 | 52 | 56 | 57 | 58 | ); 59 | }, 60 | project: { 61 | link: "https://github.com/hichemfantar/react-bluesky-embed", 62 | }, 63 | sidebar: { 64 | defaultMenuCollapseLevel: 1, 65 | }, 66 | banner: { 67 | // content: "🎉 React Bluesky Embed is now in Beta.", 68 | key: "beta", 69 | dismissible: false, 70 | 71 | // key: '3.0-release', 72 | content: ( 73 |
74 | {/* React Bluesky Embed is now in Beta.{" "} */} 75 | Contribute to Bluesky Community. 76 | 80 | Read more 81 | 82 |
83 | ), 84 | }, 85 | docsRepositoryBase: 86 | "https://github.com/hichemfantar/react-bluesky-embed/tree/main/apps/site", 87 | editLink: { 88 | content: "Edit this page on GitHub →", 89 | }, 90 | footer: { 91 | content: ( 92 |
93 |

94 | © {new Date().getFullYear()} Hichem Fantar. All rights reserved. 95 |

96 |
97 | ), 98 | }, 99 | }; 100 | 101 | export default config; 102 | -------------------------------------------------------------------------------- /apps/site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "incremental": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ] 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /apps/site/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand": "pnpm turbo build --filter=site...", 3 | "ignoreCommand": "pnpm dlx turbo-ignore" 4 | } 5 | -------------------------------------------------------------------------------- /apps/vite-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /apps/vite-app/api/postThread/[postThread].ts: -------------------------------------------------------------------------------- 1 | import type { VercelRequest, VercelResponse } from "@vercel/node"; 2 | import { getPostThread } from "react-bluesky-embed/api"; 3 | 4 | const handler = async (req: VercelRequest, res: VercelResponse) => { 5 | const postThreadId = req.query.postThread; 6 | 7 | if (req.method !== "GET" || typeof postThreadId !== "string") { 8 | res.status(400).json({ error: "Bad Request." }); 9 | return; 10 | } 11 | 12 | try { 13 | const postThread = await getPostThread(postThreadId); 14 | res.status(postThread ? 200 : 404).json({ data: postThread ?? null }); 15 | } catch (error) { 16 | console.error(error); 17 | res.status(400).json({ error: error.message ?? "Bad request." }); 18 | } 19 | }; 20 | 21 | export default handler; 22 | -------------------------------------------------------------------------------- /apps/vite-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/vite-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-app", 3 | "private": true, 4 | "license": "MIT", 5 | "repository": "https://github.com/hichemfantar/react-bluesky-embed.git", 6 | "author": "Hichem Fantar (https://bsky.app/profile/opensauced.bsky.social)", 7 | "type": "module", 8 | "scripts": { 9 | "dev": "vite", 10 | "build": "vite build", 11 | "start": "vite preview" 12 | }, 13 | "dependencies": { 14 | "@atproto/api": "^0.13.17", 15 | "clsx": "^2.1.1", 16 | "react": "^18.3.1", 17 | "react-bluesky-embed": "workspace:*", 18 | "react-dom": "^18.3.1", 19 | "react-router-dom": "^6.28.0", 20 | "swr": "^2.2.5" 21 | }, 22 | "devDependencies": { 23 | "@types/react": "^18.3.12", 24 | "@types/react-dom": "^18.3.1", 25 | "@vercel/node": "^2.9.12", 26 | "@vitejs/plugin-react-swc": "^3.0.0", 27 | "autoprefixer": "^10.4.20", 28 | "postcss": "^8.4.49", 29 | "tailwindcss": "^3.4.15", 30 | "typescript": "^4.9.3", 31 | "vite": "^4.1.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/vite-app/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /apps/vite-app/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/vite-app/readme.md: -------------------------------------------------------------------------------- 1 | # react-bluesky-embed for Vite 2 | 3 | Follow the instructions in the [official docs](https://react-bluesky-embed.vercel.app/vite) to learn more about `react-bluesky-embed` for Vite. 4 | -------------------------------------------------------------------------------- /apps/vite-app/src/base.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | @apply bg-black dark:text-gray-100; 7 | } 8 | 9 | html { 10 | color-scheme: dark; 11 | } 12 | -------------------------------------------------------------------------------- /apps/vite-app/src/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | 3 | export const Layout = () => ( 4 |
5 |
6 | 7 |
8 |
9 | ); 10 | -------------------------------------------------------------------------------- /apps/vite-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { createBrowserRouter, RouterProvider } from "react-router-dom"; 4 | import { Layout } from "./layout"; 5 | import { IndexPage } from "./pages/index"; 6 | import { PostThreadPage } from "./pages/postThread"; 7 | import "./base.css"; 8 | 9 | const router = createBrowserRouter([ 10 | { 11 | path: "/", 12 | element: , 13 | children: [ 14 | { index: true, element: }, 15 | { path: "/postThread", element: }, 16 | ], 17 | }, 18 | ]); 19 | 20 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /apps/vite-app/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { PostThread } from "react-bluesky-embed"; 2 | 3 | export const IndexPage = () => ( 4 |
5 | 16 |
17 | ); 18 | -------------------------------------------------------------------------------- /apps/vite-app/src/pages/postThread.tsx: -------------------------------------------------------------------------------- 1 | import { AtpAgent } from "@atproto/api"; 2 | import { useState } from "react"; 3 | import { 4 | EmbeddedPostThread, 5 | PostThreadNotFound, 6 | PostThreadSkeleton, 7 | Theme, 8 | } from "react-bluesky-embed"; 9 | import { 10 | getPostThread, 11 | PostThreadConfig, 12 | PostThreadParams, 13 | } from "react-bluesky-embed/api"; 14 | import { useSearchParams } from "react-router-dom"; 15 | import useSWR from "swr"; 16 | 17 | async function fetcher([params, config]: [PostThreadParams, PostThreadConfig]) { 18 | const res = await getPostThread( 19 | { 20 | did: params.did, 21 | rkey: params.rkey, 22 | }, 23 | config 24 | ); 25 | return res; 26 | } 27 | 28 | type SearchParamsObj = { 29 | did?: string; 30 | rkey?: string; 31 | }; 32 | 33 | const DEFAULT_URI = 34 | "at://did:plc:zl7kgfro2rx3pavbslhhdhuy/app.bsky.feed.post/3lblfjf4evs2v"; 35 | 36 | export const PostThreadPage = () => { 37 | const [searchParams, setSearchParams] = useSearchParams(); 38 | const [theme, setTheme] = useState("dark"); 39 | const [pastedUri, setPastedUri] = useState(DEFAULT_URI); 40 | const [resolvedUri, setResolvedUri] = useState(DEFAULT_URI); 41 | const searchParamsObj: SearchParamsObj = Object.fromEntries( 42 | searchParams.entries() 43 | ); 44 | 45 | const urlp = new URL("http://localhost:3000/api/postThread"); 46 | if (searchParamsObj.did && searchParamsObj.rkey) { 47 | urlp.searchParams.append("did", searchParamsObj.did); 48 | urlp.searchParams.append("rkey", searchParamsObj.rkey); 49 | } 50 | 51 | const { data, error, isLoading } = useSWR( 52 | () => [ 53 | { 54 | did: searchParamsObj.did, 55 | rkey: searchParamsObj.rkey, 56 | }, 57 | { 58 | depth: 6, 59 | }, 60 | ], 61 | fetcher, 62 | {} 63 | // urlp.href, 64 | // `http://localhost:3000/api/postThread/${params.uri}`, 65 | ); 66 | 67 | return ( 68 |
69 |
70 |
71 |
72 | 75 |