A reusable Svelte component library to fetch and display Bluesky post threads, including replies, directly from a post's HTTPS URL. Built with SvelteKit, TypeScript, @atproto/api
, and styled with Tailwind CSS.
https://bsky.app/profile/user.bsky.social/post/xxxxxxxxxxxxx
).npm install your-package-name @atproto/api
# or
yarn add your-package-name @atproto/api
# or
pnpm add your-package-name @atproto/api
(Replace your-package-name
with the actual name you publish this library under on npm)
Peer Dependencies:
This library relies on @atproto/api
. You also need tailwindcss
set up in your project for the default styling to work correctly.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Follow the Tailwind CSS SvelteKit installation guide if you haven't already set it up.
Import the main component and provide the postUrl
prop.
<script lang="ts">
// If using locally within the same project:
// import BlueskyPostThread from '$lib'; // Or '$lib/components/BlueskyPostThread.svelte'
// If installed as a package:
import BlueskyPostThread from 'your-package-name';
const blueskyUrl = 'https://bsky.app/profile/jay.bsky.social/post/3klp4w2ik4v2q';
</script>
<div class="container mx-auto p-4">
<h1 class="mb-4 text-2xl font-bold">Bluesky Thread</h1>
<BlueskyPostThread postUrl={blueskyUrl} />
</div>
BlueskyPostThread
)The main component accepts the following props:
Prop | Type | Default | Description |
---|---|---|---|
postUrl |
string |
Required | The full HTTPS URL of the Bluesky post (e.g., https://bsky.app/profile/handle/post/rkey ). |
agent |
AtpAgent | null |
null |
An optional, pre-configured @atproto/api agent instance. If provided, service prop is ignored. |
service |
string |
'https://public.api.bsky.app' |
The Bluesky PDS endpoint to use if no agent is provided. |
For more control over layout and styling, you can import and use the individual components:
// src/lib/index.ts (or your package entry point) exports these:
import BlueskyPostThread from './components/BlueskyPostThread.svelte'; // Default
export { default as ThreadView } from './components/ThreadView.svelte';
export { default as PostCard } from './components/PostCard.svelte';
export { default as ReplyList } from './components/ReplyList.svelte';
export { default as ReplyItem } from './components/ReplyItem.svelte';
export { default as LoadingIndicator } from './components/primitive/LoadingIndicator.svelte';
export { default as ErrorDisplay } from './components/primitive/ErrorDisplay.svelte';
export { default as NotFoundPlaceholder } from './components/primitive/NotFoundPlaceholder.svelte';
export { default as BlockedPlaceholder } from './components/primitive/BlockedPlaceholder.svelte';
export { getPostDataFromHttpsUrl } from './api_stuff';
export type { ReplyUnion } from './types';
Example (Conceptual):
<script lang="ts">
import { PostCard, LoadingIndicator, ErrorDisplay } from 'your-package-name';
import { getPostDataFromHttpsUrl } from 'your-package-name';
import { onMount } from 'svelte';
import type { AppBskyFeedDefs } from '@atproto/api';
let postViewPromise: Promise<AppBskyFeedDefs.PostView | null>; // Example: Fetch only the main post view
// Simplified fetch logic for example purposes
onMount(async () => {
// You would need more robust logic here similar to the main component
try {
const thread = await getPostDataFromHttpsUrl('some-url');
postViewPromise = Promise.resolve(thread?.post ?? null);
} catch (err) {
postViewPromise = Promise.reject(err);
}
});
</script>
{#if postViewPromise}
{#await postViewPromise}
<LoadingIndicator />
{:then postView}
{#if postView}
<div class="my-custom-wrapper bg-blue-100 p-5">
<PostCard {postView} size="large" />
</div>
{:else}
<p>Post not found.</p>
{/if}
{:catch error}
<ErrorDisplay {error} />
{/await}
{/if}
Refer to the source code of BlueskyPostThread.svelte
and ThreadView.svelte
to see how the primitives are composed.
You can use the underlying data-fetching logic in any JavaScript or TypeScript project (Node.js, React, Vue, Vanilla JS, etc.). You will need to handle the rendering yourself.
Install the core dependency:
npm install @atproto/api
# or yarn add / pnpm add
Copy or Adapt the API Logic:
Take the src/lib/api_stuff.ts
file from this library (or install the library and import the function if using Node.js/bundler). Ensure you have the necessary types (like ReplyUnion
from src/lib/types.ts
if needed).
Use the getPostDataFromHttpsUrl
function:
// Example in a generic async context (e.g., Node.js script, framework component method)
// If installed as a package and exported:
// import { getPostDataFromHttpsUrl } from 'your-package-name';
// Or if you copied the file:
import { getPostDataFromHttpsUrl } from './path/to/api_stuff'; // Adjust path
import { AppBskyFeedDefs } from '@atproto/api'; // For typing the result and using type guards
async function displayBlueskyThread(url: string) {
console.log(`Fetching thread for: ${url}`);
try {
// This function handles URL parsing, handle resolution, and fetching
const threadData: AppBskyFeedDefs.ThreadViewPost = await getPostDataFromHttpsUrl(url);
console.log('Successfully fetched thread data!');
// --- Render the data using your framework/UI logic ---
// Example: Accessing main post text
if (AppBskyFeedDefs.isPostView(threadData.post)) {
const record = threadData.post.record as AppBskyFeedDefs.PostRecord; // Type assertion often needed
console.log(`Main Post by ${threadData.post.author.handle}: ${record?.text}`);
}
// Example: Looping through replies
if (threadData.replies && threadData.replies.length > 0) {
console.log('\nReplies:');
threadData.replies.forEach((reply) => {
if (AppBskyFeedDefs.isThreadViewPost(reply)) {
const postView = reply.post;
const record = postView.record as AppBskyFeedDefs.PostRecord;
console.log(`- Reply by ${postView.author.handle}: ${record?.text}`);
// Recursively render reply.replies if needed
} else if (AppBskyFeedDefs.isNotFoundPost(reply)) {
console.log(`- [Deleted Reply: ${reply.uri}]`);
} else if (AppBskyFeedDefs.isBlockedPost(reply)) {
console.log(`- [Blocked Reply: ${reply.uri}]`);
}
});
}
// --- End Rendering Logic ---
} catch (error) {
console.error('Failed to fetch or display Bluesky thread:', error);
// Display the error appropriately in your UI
}
}
// Example call
displayBlueskyThread('https://bsky.app/profile/jay.bsky.social/post/3klp4w2ik4v2q');
The components are styled using Tailwind CSS utility classes. For the components to render correctly, your project must have Tailwind CSS configured, and Tailwind's base styles must be included.
If you don't use Tailwind, you can either:
tailwind.config.js
.<style>
blocks, CSS Modules, etc.).Contributions are welcome! Please feel free to open an issue or submit a pull request. (Add more specific contribution guidelines if desired).
MIT (Choose and include an appropriate license file)