X Embed
X (formerly Twitter) is a social media platform for sharing posts.
<ScriptXEmbed> fetches post data through your Nuxt server and exposes it through slots. Post JSON and images pass through your origin rather than loading X's widget JavaScript.
Nuxt Config Setup
Add this to your nuxt.config.ts to register the server routes that power <ScriptXEmbed>:
export default defineNuxtConfig({
scripts: {
registry: {
xEmbed: {},
}
}
})This script's proxy endpoints use HMAC URL signing when you configure a NUXT_SCRIPTS_PROXY_SECRET. See the security guide for setup instructions.
This registers the required server API routes (/_scripts/embed/x and /_scripts/embed/x-image) that handle fetching tweet data and proxying images.
<ScriptXEmbed>
The post endpoint caches syndication responses for 10 minutes and rewrites profile photos, attached photos, entity media, quoted-post images, and video posters through the image endpoint. Video variant URLs remain unchanged, so rendering one in a <video> element makes a direct browser request to X.
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
I'm told there are some who read my music choices while writing @Cloudflare's quarterly earnings for hints. Unfortunately, this time I'm sitting in a New York hotel lobby and don't have control over the music. But I do have a crackling real-wood fire — which is pretty awesome!
Slot Props
The default slot receives the following props:
interface SlotProps {
// Raw data
tweet: XEmbedTweetData
// User info
userName: string
userHandle: string
userAvatar: string // Proxied URL
isVerified: boolean | undefined
// Tweet content
text: string
// Formatted values
datetime: string // "12:47 PM · Feb 5, 2024"
createdAt: Date
likes: number
likesFormatted: string // "1.2K"
replies: number
repliesFormatted: string // "234"
// Media
photos?: Array<NonNullable<XEmbedTweetData['photos']>[number] & {
proxiedUrl: string
}>
video: {
poster: string
posterProxied: string
variants: Array<{ type: string, src: string }>
} | null
// Links
tweetUrl: string
userUrl: string
// Quote tweet
quotedTweet?: XEmbedTweetData
// Reply context
isReply: boolean
replyToUser?: string
// Helpers
proxyImage: (imageUrl: string) => string
}
Named Slots
| Slot | Description |
|---|---|
default | Main content with slot props |
loading | Shown while fetching tweet data |
error | Shown if tweet fetch fails, receives { error } |
Data flow
The implementation follows Cloudflare Zaraz's server-rendered embed approach. No X JavaScript runs in the page, and X does not receive the visitor's IP address for post JSON or proxied images. Rendered video variants and links to X still contact X directly.
tweetIdstring required The tweet ID to embed.
apiEndpointstring = '/_scripts/embed/x'Optional: Custom API endpoint for fetching tweet data.
imageProxyEndpointstring = '/_scripts/embed/x-image'Optional: Custom image proxy endpoint.
