# 从 S3/R2 加载的自定义表情符号绕过 CDN 路由

**URL:** https://meta.discourse.org/t/custom-emojis-loaded-from-s3-r2-bypass-cdn-routing/406384
**Category:** Bug
**Tags:** emoji, cdn, s3, fixed
**Created:** [2026年六月29日 19:50 UTC](https://meta.discourse.org/t/custom-emojis-loaded-from-s3-r2-bypass-cdn-routing/406384 "2026-06-29T19:50:56Z")
**Posts on this page:** 1
**Showing post:** 1

<div class="post-metadata">

### Author: ![Lilly](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/lilly/32/575047_2.png) [@Lilly](https://meta.discourse.org/u/Lilly)
#### Post date: [2026年六月29日 19:50 UTC](https://meta.discourse.org/t/custom-emojis-loaded-from-s3-r2-bypass-cdn-routing/406384/1 "2026-06-29T19:50:56Z")

</div>

### 概述

当使用 S3 或 Cloudflare R2 进行上传并配合自定义 CDN URL 时，自定义表情符号的上传不会遵循 CDN 配置，而是尝试直接从原始存储桶 URL 加载。

### 问题描述

当管理员上传自定义表情符号时，上传程序会创建一条 `upload` 记录，并将原始存储桶 URL 保存到数据库中（例如 `//my-bucket.s3.amazonaws.com/...` 或 `//my-bucket.r2.cloudflarestorage.com/...`）——这是 Discourse 的标准行为。

然而，当 `app/models/emoji.rb` 为 `/site.json` 生成表情符号缓存时，它会将 `upload.url` 直接传递给 `emoji` 对象：

```plaintext
e.url = emoji.upload&.url

```

由于跳过了 CDN 辅助方法，前端接收到了原始存储桶 URL。因此，根据存储桶访问策略的严格程度，这会导致图片无法显示，或者迫使 Discourse 通过内部的 `avatar_proxy` 路由表情符号。

### 解决方案

我提交了一个 PR，将 URL 赋值操作包裹在 `Discourse.store.cdn_url()` 中，这使得自定义表情符号加载器的行为与标准帖子图片和头像的路由方式保持一致。

> <https://github.com/discourse/discourse/pull/41267>
>
> When a Discourse instance is configured to use S3 (or S3-compatible storage like… Cloudflare R2) for uploads, the Custom Emoji uploader saves the raw bucket URL to the \`uploads\` table (e.g., \`//my-bucket.s3.amazonaws.com/...\`).
> 
> Currently, when the Emoji cache is generated for \`/site.json\`, \`app/models/emoji.rb\` passes \`emoji.upload&.url\` directly. If a dedicated App CDN (\`DISCOURSE\_CDN\_URL\`) is not configured, this causes newly uploaded Custom Emojis to completely bypass the configured \`DISCOURSE\_S3\_CDN\_URL\` and attempt to load directly from the raw bucket URL, resulting in broken images. 
> 
> Additionally, during live uploads via the Admin UI, the API response returns a broken relative path, resulting in a broken image preview until the page is forcibly refreshed.
> 
> \*\*Why \`DISCOURSE\_CDN\_URL\` is intentionally omitted in this architecture:\*\*
> While the standard advice is to configure an App CDN, this is physically impossible for admins who proxy their main domain through Cloudflare to handle their app caching. 
> If an admin attempts to define a secondary \`DISCOURSE\_CDN\_URL\` (e.g., \`cdn.forum.com\`) using a standard Cloudflare DNS record, Cloudflare cannot rewrite the \`Host\` header to match the origin domain (to prevent Domain Fronting). Because the \`Host\` header doesn't match \`DISCOURSE\_HOSTNAME\`, Discourse's internal NGINX server rejects the CDN requests with a \`301 Redirect\`, instantly triggering CORS errors and breaking the site's Javascript. 
> 
> Therefore, admins using the Cloudflare Proxy + S3/R2 architecture \*must\* leave \`DISCOURSE\_CDN\_URL\` blank while setting \`DISCOURSE\_S3\_CDN\_URL\`.
> 
> \*\*The Solution:\*\*
> This PR wraps the assignment in \`Discourse.store.cdn\_url()\` so that Custom Emoji uploads obey the same S3 CDN routing and fallback logic as standard post image uploads. This ensures the emojis route to the S3 CDN successfully, and fixes the broken API response generation during live uploads at the backend level.
> 
> \*\*Files touched:\*\*
> \* \`app/models/emoji.rb\`
> \* \`spec/models/emoji\_spec.rb\`

### 临时修复方案

在等待该 PR 被审核和合并期间，我创建了一个轻量级的主题组件，它在自定义表情符号渲染到 DOM 之前，将原始存储桶 URL 替换为正确的 CDN URL（适用于帖子和聊天）。

如果你的站点正在经历此问题，你可以安装此组件，并在主题管理设置中配置你的 S3 字符串，以修复任何损坏的自定义表情符号：

> **[GitHub - Lillinator/s3-fix-custom-emojis: provides a fix for the s3 object storage bug...](https://github.com/Lillinator/s3-fix-custom-emojis)**
>
> provides a fix for the s3 object storage bug related to custom emoji rendering

**注意：** 如果你已经上传了目前损坏的自定义表情符号，在容器中运行 `discourse remap "//my-raw-bucket-url.com" "https://my-cdn.com"` 将修复数据库中旧的记录，而主题组件将修复任何新上传的表情符号，直到 PR 修复被合并到核心代码中。

---

_[View the full topic](https://meta.discourse.org/t/custom-emojis-loaded-from-s3-r2-bypass-cdn-routing/406384)._
