---
title: "在 Astro 中轻松嵌入 arXiv 论文及其他 PDF 文件"
description: "我为我的 Astro 站点创建了一个简单的 Preact 组件，用于渲染 PDF 文档（如 arXiv 论文），只需使用 arXiv ID 或直链即可。这使得未来在讨论、笔记或分析时能够无缝集成论文，并避免使用 iframe 以实现更好的设计控制。"
pubDate: "2025-04-19"
categories: ["Astro", "Preact", "PDF.js", "arXiv", "JavaScript", "Web 开发"]
---

我开发了一个可复用的 Preact 组件，可以在我的博客中轻松嵌入 PDF 文件，特别是 arXiv 论文。该组件允许仅通过 arXiv ID 或 PDF 的直接链接即可在客户端渲染文档，从而无缝地将学术论文集成到我的内容中，为未来的讨论、笔记或分析提供了便利。

我的主要目标是创建一个工具，让我在撰写讨论论文的博文时能方便地展示这些文件，而不是依赖可能影响设计的标准 `<iframe>` 元素。

## 为何选择自定义组件而非 `<iframe>`？

虽然使用 `<iframe>` 指向 PDF 源链接是一种方法，但它在外观和行为控制方面存在局限性。我需要一种更集成化的方案，以实现以下目标：

1.  **简约设计**：我希望 PDF 查看器在设计上融入博客主题，避免 `<iframe>` 查看器常带有的浏览器默认界面。
2.  **自定义能力**：直接使用 `PDF.js` 可以更精细地控制渲染，例如应用主题特定样式（如我实现的暗黑模式调整）或未来可能添加自定义覆盖层或注释。
3.  **避免嵌套上下文**：Iframes 会创建独立的浏览上下文，有时会使交互或样式一致性变得复杂。

通过围绕 `PDF.js` 构建组件，我实现了一个轻量级、可定制的查看器，感觉像是页面的自然组成部分。

## 解决方案：客户端 PDF 组件

我构建了一个 Preact 组件（`PDFViewer.tsx`），并指定它仅在浏览器中运行。这一点至关重要，因为像 `pdfjs-dist` 这样的 PDF 渲染库通常依赖于浏览器特有的 API（如 `DOMMatrix`），这些 API 在 Astro 的服务器端渲染（SSR）期间不可用。使用 Astro 的 `client:only="preact"` 指令可确保该组件及其依赖项仅在客户端加载。

### 使用示例

嵌入 PDF 现在变得非常简单。

**1. 使用 arXiv ID：**

要显示一篇 arXiv 论文，我只需要其 ID（例如，ResNet 论文的 ID `1512.03385`）：

```jsx
import PDFViewer from "@/components/islands/common/PDFViewer.tsx";

// 渲染 arXiv 论文 1512.03385 的 PDF
<PDFViewer paperId="1512.03385" client:only="preact" />;
```

import PDFViewer from "~/components/islands/common/PDFViewer";

<PDFViewer paperId="1512.03385" client:only="preact" />

**2. 使用直接链接：**

该组件也接受指向任何 PDF 文件的直接链接（直链）。

```jsx
import PDFViewer from "@/components/islands/common/PDFViewer.tsx";

// 从指定 URL 渲染 PDF
<PDFViewer
  url="https://your-bucket.s3.amazonaws.com/path/to/document.pdf"
  client:only="preact"
/>;
```

**关于直链和 CORS 的重要说明**：当使用 url 属性加载托管在 AWS S3 或其他域上的 PDF 时，可能会遇到跨源资源共享（CORS）问题。浏览器的安全策略会阻止 JavaScript 从不同源获取资源，除非该源通过 CORS 头明确允许。您必须配置托管服务（例如您的 S3 存储桶）以发送适当的 Access-Control-Allow-Origin 头，允许您的网站域获取 PDF 文件。

## 工作原理

- **arXiv ID 处理**：提供 paperId 时，组件会构建一个本地路径，如 /papers/1512.03385。服务器重定向（在我的案例中使用了 Netlify 重定向）将此路径映射到实际的 `https://arxiv.org/pdf/:id.pdf` URL。这会在请求时直接从 arXiv 获取论文，避免了版权问题，并保持了组件使用的简洁性。

Netlify 重定向配置示例：

```toml
[[redirects]]
from   = "/papers/:id.pdf"
to     = "https://arxiv.org/pdf/:id.pdf"
status = 200
force  = true

[[redirects]]
from   = "/papers/:id"
to     = "https://arxiv.org/pdf/:id.pdf"
status = 200
force  = true
```

- **URL 处理**：如果提供了 url（直接链接），它将直接传递给 PDF.js，但需遵守上述 CORS 规则。

- **客户端渲染**：这个 Astro 指令阻止了服务器端渲染。pdfjs-dist 库在 Preact 组件的 useEffect 钩子中动态导入，确保它只在浏览器中运行。

- **渲染**：组件使用 pdfjs-dist 加载 PDF，并将每一页渲染到 `<canvas>` 元素上。它包含加载/错误状态，并使用 CSS 滤镜根据网站主题调整其外观（例如暗黑模式）。

```d2 title="使用 arXiv ID 的 PDF.js 流程图"
style: {
  fill: transparent
}

direction: down

UserBrowser: "用户 (浏览器)"
AstroMDX: "Astro/MDX 页面"
PDFViewerClient: "PDFViewer 组件 (客户端)" {
  shape: hexagon
}
NetlifyRedirects: "服务器重定向 (例如 Netlify)" {
  shape: cloud
}
arXivServer: "arXiv 服务器" {
  shape: cylinder
}
PDFjsLib: "PDF.js 库" {
  shape: hexagon
}
Canvas: "Canvas 元素"

UserBrowser -> AstroMDX: "1. 访问页面"
AstroMDX -> PDFViewerClient: "2. 包含组件\n(带 arXiv ID)"
PDFViewerClient -> UserBrowser: "3. 构建路径\n/papers/:id"
UserBrowser -> NetlifyRedirects: "4. 请求 /papers/:id"
NetlifyRedirects -> arXivServer: "5. 重定向请求\n至 arxiv.org/pdf/:id.pdf"
arXivServer -> UserBrowser: "6. 提供 PDF"
PDFViewerClient -> PDFjsLib: "7. 加载 PDF.js"
PDFjsLib -> UserBrowser: "8. 获取 PDF 数据\n(通过浏览器)"
PDFjsLib -> Canvas: "9. 渲染 PDF"
Canvas -> UserBrowser: "10. 显示 PDF"
```

```d2 title="使用直接链接的 PDF.js 流程图"
style: {
  fill: transparent
}

direction: down

UserBrowser: "用户 (浏览器)"
AstroMDX: "Astro/MDX 页面"
PDFViewerClient: "PDFViewer 组件 (客户端)" {
  shape: hexagon
}
ExternalHost: "外部 PDF 托管 (例如 S3)" {
  shape: cylinder
}
CORSCheck: "浏览器 CORS 检查" {
  shape: diamond
}
PDFjsLib: "PDF.js 库" {
  shape: hexagon
}
Canvas: "Canvas 元素"
ErrorState: "显示错误"

UserBrowser -> AstroMDX: "1. 访问页面"
AstroMDX -> PDFViewerClient: "2. 包含组件\n(带直接链接)"
PDFViewerClient -> PDFjsLib: "3. 加载 PDF.js"
PDFjsLib -> ExternalHost: "4. 从 URL 请求 PDF"
ExternalHost -> CORSCheck: "5. 响应 PDF 及 CORS 头"
CORSCheck -> PDFjsLib: "6a. CORS 成功" {
  style: {
    stroke: "#008000"
  }
}
CORSCheck -> ErrorState: "6b. CORS 失败" {
  style: {
    stroke: "#FF0000"
    stroke-dash: 4
  }
}
ErrorState -> UserBrowser: "7b. 显示错误" {
  style: {
    stroke: "#FF0000"
    stroke-dash: 4
  }
}
PDFjsLib -> Canvas: "7a. 渲染 PDF" {
   style: {
    stroke: "#008000"
  }
}
Canvas -> UserBrowser: "8a. 显示 PDF" {
   style: {
    stroke: "#008000"
  }
}
```

## 优势

- **简单性**：只需一个组件标签即可嵌入。
- **灵活性**：适用于 arXiv 论文和其他托管的 PDF（需正确配置 CORS）。
- **集成性**：提供了一个简约的查看器，比 iframe 更能匹配网站设计。
- **性能**：将繁重的 PDF 渲染推迟到客户端执行。
- **面向未来**：为未来讨论论文的博文实现更丰富的交互提供了基础。

## 结论

这个定制的 Preact 组件为我提供了一种简洁高效的方式，将 PDF 文档直接嵌入到我的 Astro 站点中。它避免了 iframe 的局限性，提供了更好的设计集成，并为未来我能够直接在内容中引用和讨论学术论文的博文铺平了道路。请记住，在通过直接链接到外部托管的 PDF 时，需要适当地处理 CORS 设置。
