Skip to main content
Scripts

Bluesky Embed

Bluesky is a decentralized social media platform built on the AT Protocol.

<ScriptBlueskyEmbed> fetches post data through your Nuxt server and exposes scoped slots, so the markup and styling stay in your app. The upstream request uses Bluesky's getPostThread API.

Bluesky Embed

View source

Nuxt Config Setup

Add this to your nuxt.config.ts to register the server routes that power <ScriptBlueskyEmbed>:

export default defineNuxtConfig({
  scripts: {
    registry: {
      blueskyEmbed: {},
    }
  }
})

This script's proxy endpoints use HMAC URL signing when you configure a NUXT_SCRIPTS_PROXY_SECRET. See the security guide for setup instructions.

Enabling the integration registers /_scripts/embed/bluesky for post data and /_scripts/embed/bluesky-image for images.

<ScriptBlueskyEmbed>

The post endpoint caches thread responses for 10 minutes and handle-to-DID lookups for 24 hours. It rewrites avatars, post images, and external-card thumbnails through the image endpoint. The component also turns rich-text facets into links for the richText slot prop.

richText is escaped HTML, but the current facet converter does not restrict link URI schemes. If you embed posts you do not control, render the plain text prop or sanitize richText with an allowlist that rejects schemes such as javascript:.

The image proxy validates the initial hostname but currently follows redirects without validating each destination. Do not treat that allowlist as a complete SSRF boundary until redirect targets are checked too.

Demo

Nuxt 4.5 is out, and it's a big one. 🚀 - ⚡️ Vite 8 - 🦀 Rspack 2, now powered by Rsbuild - 🌊 experimental SSR streaming for faster TTFB - 🩺 a new stable error code system - 🎨 useLayout, named views, and more ... plus a lot of groundwork for Nuxt 5! 💚💚💚
7:37 PM · Jul 18, 2026134 likes19 reposts

Slot Props

The default slot receives the following props:

interface SlotProps {
  // Raw data
  post: BlueskyEmbedPostData
  // Author info
  displayName: string
  handle: string
  avatar: string // Proxied URL
  isVerified: boolean
  // Post content
  text: string // Plain text
  richText: string // HTML with links, mentions, and hashtags
  langs?: string[] // Language codes
  // Formatted values
  datetime: string // "12:47 PM · Feb 5, 2024"
  createdAt: Date
  likes: number
  likesFormatted: string // "1.2K"
  reposts: number
  repostsFormatted: string // "234"
  replies: number
  repliesFormatted: string // "42"
  quotes: number
  quotesFormatted: string // "12"
  // Media
  images?: Array<{
    thumb: string // Proxied thumbnail URL
    fullsize: string // Proxied full-size URL
    alt: string
    aspectRatio?: { width: number, height: number }
  }>
  externalEmbed?: {
    uri: string
    title: string
    description: string
    thumb?: string // Proxied URL
  }
  // Links
  postUrl: string
  authorUrl: string
  // Helpers
  proxyImage: (url: string) => string
}

Named Slots

SlotDescription
defaultMain content with slot props
loadingShown while fetching post data
errorShown if post fetch fails, receives { error }

Data flow

No Bluesky JavaScript runs in the page. Post JSON and images reach the browser from your origin, so Bluesky does not receive the visitor's IP address for those requests. Links in your rendering still open Bluesky when clicked.

Author Opt-Out

The endpoint rejects posts or authors carrying Bluesky's !no-unauthenticated label with a 403 response, and the component shows the error slot. The label means the content should be unavailable to logged-out users in clients that honor it; it is broader than an embed-only preference.

postUrlstring required

The Bluesky post URL to embed.

apiEndpointstring = '/_scripts/embed/bluesky'

Custom API endpoint for fetching post data.

imageProxyEndpointstring = '/_scripts/embed/bluesky-image'

Custom image proxy endpoint.