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.
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
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
| Slot | Description |
|---|---|
default | Main content with slot props |
loading | Shown while fetching post data |
error | Shown 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.