npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@svebcomponents/atproto.comments

v0.7.1

Published

Web component rendering ATProto/Bluesky threads as blog comments

Readme

@svebcomponents/atproto.comments

A hydratable web component that renders an ATProto post thread as a live comment section. Reads Bluesky's public AppView and links to bsky.app out of the box; appview and viewer point it at any other.

Four tiers, in order of how much you want to set up. Each one builds on the last — self-hosting the backend is a switch you can flip from any of them.

1. CDN — paste two tags, no install

<script
  type="module"
  src="https://cdn.jsdelivr.net/npm/@svebcomponents/atproto.comments"
></script>

<atproto-comments
  thread="https://bsky.app/profile/bsky.app/post/..."
></atproto-comments>

For a static site, CMS, or anywhere you can only paste HTML. No bundler, no node_modules. jsDelivr mirrors whatever's on npm, so a version pin (@svebcomponents/[email protected]) works the same way.

2. npm — bundled into your app

pnpm add @svebcomponents/atproto.comments
import "@svebcomponents/atproto.comments";
<atproto-comments
  thread="https://bsky.app/profile/bsky.app/post/..."
></atproto-comments>

Same markup, served from your own build instead of a CDN — useful once you want the component tree-shaken and versioned alongside the rest of your dependencies.

3. SSR (server-rendered, hydrated in place)

Install @svebcomponents/ssr 0.8.2 or newer, add its Vite plugin, and import this package's ./ssr entry once on the server:

// hooks.server.ts
import "@svebcomponents/atproto.comments/ssr";

The renderer knows its own tag and registers itself, so there is nothing to wire up by hand. During asynchronous SSR the component fetches the thread and serializes it for hydration, so the first paint already has comments instead of the loading skeleton. Passing threadData supplies that snapshot explicitly instead.

Host adapters ship upstream for SvelteKit, React 19, Vue 3, and Astro (@svebcomponents/ssr*); Nuxt and SolidStart integrations are still planned. React Server Components, including Next.js App Router, require @svebcomponents/ssr-react 0.4.0 or newer.

4. Self-hosted backend

All three tiers above use the free hosted backend (https://atproto.svebcomponents.dev/atproto) for OAuth, posting, and live events by default. Set the service property to point at your own deployment instead — see Self-hosting below. This is independent of which tier you're on: even the plain CDN drop-in can point service at a self-hosted backend.

That covers the complete hosted setup at tiers 1–2. Public thread reads go directly to the public AppView, and ATProto OAuth credentials never enter the browser.

Live updates

The component does not poll or use a time-based stale interval. It renders its initial snapshot, opens one hosted SSE stream, and refreshes the thread:

  • once when the stream reports that its Spacedust upstream is connected;
  • when a newly created reply event arrives;
  • after a reconnect or a hidden tab becomes visible again.

Events are coalesced and a newly posted URI is retried briefly while the public AppView indexes it. A failed background refresh leaves the current comments visible.

Call element.revalidate() to request a manual refresh. Concurrent refreshes for the same thread are deduplicated.

Self-hosting

The service property is the single backend switch. It moves OAuth, posting, and SSE together:

<atproto-comments
  thread="at://did:plc:.../app.bsky.feed.post/..."
  service="/atproto"
></atproto-comments>

A same-origin @svebcomponents/atproto.bridge deployment can use an HttpOnly cookie with sessionMode: "cookie". A cross-origin deployment should use the default origin-bound bearer session. Set service="" for a read-only component with no live backend, or use readonly to hide posting while keeping live updates.

Properties

| Property | Default | Description | | ------------- | -------------- | ------------------------------------------------------------------------------------------ | | thread | — | AT URI or any viewer's /profile/…/post/… URL | | service | hosted | OAuth, posting, and SSE bridge URL | | readonly | false | hide sign-in and in-page replies | | live | signed-in | who gets live updates: signed-in, all, or off | | show-root | false | render the discussion root's own post above the replies | | max-depth | 6 | maximum nested depth | | sort | oldest | oldest, newest, or likes | | labels | collapse | hide, collapse, or show | | viewer | bsky.app | outbound profile/post viewer | | viewer-name | viewer's host | what outbound links call the viewer ("Reply on …") | | appview | public AppView | public thread-read endpoint | | page-url | — | embedding page's canonical URL; enables no-JS sign-in on a same-origin cookie-mode service | | fetched-at | — | when threadData was fetched (epoch ms or ISO); stamped automatically by SSR prefetch | | stale-time | 60000 | ms a preloaded snapshot is trusted before one client refresh; Infinity disables |

threadData is a JavaScript-only CommentTree property for a preloaded snapshot.

With the default live="signed-in", only readers who sign in hold a connection to the service; signed-out visitors read the thread straight from the AppView. Set live="all" to stream for everyone (reasonable when the bridge is your own), or live="off" to disable the stream entirely.

"Not live" does not mean "stale": when the component mounts with a preloaded snapshot older than stale-time, it runs one background revalidation against the public AppView, so a signed-out reader still sees current comments without ever connecting to the service. The SSR prefetch stamps fetched-at automatically; hosts passing their own threadData can supply it too (or set stale-time={Infinity} to trust their snapshot until a live event arrives).

Events

  • atproto-comments:loaded
  • atproto-comments:revalidated
  • atproto-comments:comment
  • atproto-comments:live-status
  • atproto-comments:signed-in
  • atproto-comments:posted
  • atproto-comments:error

Every event's detail is documented on the component itself and reaches the published types as CustomEvent<unknown> — they are dispatched through one shared helper, which the build's source scan cannot narrow.

Editor and TypeScript support

The package ships a custom elements manifest at custom-elements.json, which editors read for HTML completions on <atproto-comments>.

TypeScript users need no setup beyond importing the package — the declarations put the element in HTMLElementTagNameMap and narrow addEventListener:

import "@svebcomponents/atproto.comments";

const comments = document.querySelector("atproto-comments");
comments?.addEventListener("atproto-comments:loaded", (event) => {
  console.log(event.detail);
});

Svelte, React and Vue template types are not registered automatically: the standalone build bundles Svelte so the package can be used in applications that have none. Register them yourself with the exported AtprotoCommentsElement, AtprotoCommentsAttributes, AtprotoCommentsEventMap and AtprotoCommentsEventHandlers types — see typing elements in React & Vue.

See atproto.svebcomponents.dev for the full reference and live demo.