---
title: "X 嵌入"
description: "在服务器端渲染 X 帖子，无需直接从浏览器向 X 请求帖子 JSON 或代理图片。"
canonical_url: "https://nuxt-scripts.zhcndoc.com/scripts/x-embed"
last_updated: "2026-08-11T09:33:13.571Z"
---

[X（前身为 Twitter）](https://x.com) 是一个用于分享帖子的社交媒体平台。

[`<ScriptXEmbed>`](/scripts/x-embed) 通过你的 Nuxt 服务器获取帖子数据，并通过插槽将其公开。帖子 JSON 和图片会通过你的源站传递，而不是加载 X 的小组件 JavaScript。

<script-stats>



</script-stats>

<script-docs :embed="true">



</script-docs>

这会注册所需的服务器 API 路由（`/_scripts/embed/x` 和 `/_scripts/embed/x-image`），用于处理推文数据的获取和图片代理。

## [`<ScriptXEmbed>`](/scripts/x-embed)

帖子端点会缓存联合响应 10 分钟，并通过图片端点重写个人资料照片、附加照片、实体媒体、引用帖子图片和视频海报。视频变体 URL 保持不变，因此在 `<video>` 元素中渲染视频变体时，浏览器会直接向 X 发起请求。

<callout color="amber">

图片代理会验证初始主机名，但目前会在不验证每个目标的情况下跟随重定向。在重定向目标也经过检查之前，不要将该允许列表视为完整的 SSRF 边界。

</callout>

### 演示

<code-group>
<x-embed-demo label="输出">



</x-embed-demo>

```vue [基础用法]
<template>
  <ScriptXEmbed tweet-id="1754336034228171055">
    <template #default="{ userName, userHandle, text, datetime, likesFormatted }">
      <div class="border rounded-lg p-4 max-w-md">
        <p class="font-bold">
          {{ userName }} (@{{ userHandle }})
        </p>
        <p>{{ text }}</p>
        <p class="text-gray-500 text-sm">{{ datetime }} - {{ likesFormatted }} 赞</p>
      </div>
    </template>
  </ScriptXEmbed>
</template>
```

```vue [样式化推文卡片]
<template>
  <ScriptXEmbed tweet-id="1754336034228171055">
    <template #default="{ userName, userHandle, userAvatar, text, datetime, likesFormatted, repliesFormatted, photos, isVerified }">
      <div class="max-w-lg bg-white dark:bg-gray-800 rounded-xl border p-4">
        <!-- 头部 -->
        <div class="flex items-start gap-3 mb-3">
          <img :src="userAvatar" :alt="userName" class="w-12 h-12 rounded-full">
          <div>
            <span class="font-bold">{{ userName }}</span>
            <span v-if="isVerified" class="text-blue-500 ml-1">✓</span>
            <p class="text-gray-500">
              @{{ userHandle }}
            </p>
          </div>
        </div>
        <!-- 内容 -->
        <p class="mb-3 whitespace-pre-wrap">{{ text }}</p>
        <!-- 图片 -->
        <div v-if="photos?.length" class="mb-3 rounded-xl overflow-hidden">
          <img v-for="photo in photos" :key="photo.url" :src="photo.proxiedUrl" class="w-full">
        </div>
        <!-- 底部 -->
        <div class="flex items-center gap-4 text-gray-500 text-sm">
          <span>{{ datetime }}</span>
          <span>{{ repliesFormatted }} 条回复</span>
          <span>{{ likesFormatted }} 赞</span>
        </div>
      </div>
    </template>

    <template #loading>
      <div class="animate-pulse bg-gray-100 rounded-xl p-4 max-w-lg">
        加载推文中...
      </div>
    </template>

    <template #error>
      <div class="bg-red-50 border border-red-200 rounded-xl p-4 max-w-lg">
        加载推文失败
      </div>
    </template>
  </ScriptXEmbed>
</template>
```

</code-group>

### 属性

`ScriptXEmbed` 组件接受以下属性：

<table>
<thead>
  <tr>
    <th>
      属性
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      默认值
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        tweetId
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      必填
    </td>
    
    <td>
      要嵌入的推文 ID
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        apiEndpoint
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        /api/_scripts/x-embed
      </code>
    </td>
    
    <td>
      获取推文数据的自定义 API 端点
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        imageProxyEndpoint
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        /api/_scripts/x-embed-image
      </code>
    </td>
    
    <td>
      代理图片的自定义端点
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rootAttrs
      </code>
    </td>
    
    <td>
      <code>
        HTMLAttributes
      </code>
    </td>
    
    <td>
      <code>
        {}
      </code>
    </td>
    
    <td>
      根元素属性
    </td>
  </tr>
</tbody>
</table>

### 插槽属性

默认插槽接收以下属性：

```ts
interface SlotProps {
  // 原始数据
  tweet: XEmbedTweetData
  // 用户信息
  userName: string
  userHandle: string
  userAvatar: string // 代理后的 URL
  isVerified: boolean | undefined
  // 推文内容
  text: string
  // 格式化后数据
  datetime: string // "12:47 PM · Feb 5, 2024"
  createdAt: Date
  likes: number
  likesFormatted: string // "1.2K"
  replies: number
  repliesFormatted: string // "234"
  // 媒体
  photos?: Array<NonNullable<XEmbedTweetData['photos']>[number] & {
    proxiedUrl: string
  }>
  video: {
    poster: string
    posterProxied: string
    variants: Array<{ type: string, src: string }>
  } | null
  // 链接
  tweetUrl: string
  userUrl: string
  // 引用推文
  quotedTweet?: XEmbedTweetData
  // 回复上下文
  isReply: boolean
  replyToUser?: string
  // 辅助方法
  proxyImage: (imageUrl: string) => string
}
```

### 命名插槽

<table>
<thead>
  <tr>
    <th>
      插槽
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        default
      </code>
    </td>
    
    <td>
      主内容，带插槽属性
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        loading
      </code>
    </td>
    
    <td>
      获取推文数据时展示
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        error
      </code>
    </td>
    
    <td>
      推文数据获取失败时展示，接收 <code>
        { error }
      </code>
    </td>
  </tr>
</tbody>
</table>

## 数据流

该实现遵循 [Cloudflare Zaraz 的服务器渲染嵌入方法](https://blog.cloudflare.com/zaraz-supports-server-side-rendering-of-embeds/)。页面中不会运行任何 X JavaScript，并且 X 不会收到访问者的 IP 地址，也不会收到帖子 JSON 或代理图片的请求。渲染后的视频变体以及指向 X 的链接仍会直接联系 X。

<script-types>



</script-types>
