关于 react-pdf
使用 React-PDF 以 TypeScript 和 JSX 生成 PDF 的技能指南,涵盖文档组件、布局、字体、渲染与预览,并说明确保输出可靠的关键限制。
主要功能
- 01使用 React-PDF 组件组合文档
- 02应用可复用样式和布局
- 03注册本地字体
- 04将 PDF 渲染为文件或缓冲区
- 05添加页码和固定页眉
- 06将页面转换为图像预览
适用场景
- 01从 JSX 生成分页文档
- 02使用自定义本地字体制作 PDF
- 03检查分页和渲染结果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ molefrog/ skills --skill react-pdf安装后在 Claude Code 中输入/react-pdf确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/example-template.tsxreferences/components.mdgoogle-fonts.txt详细介绍
使用 React-PDF 生成 PDF
关键要求
- 字体必须是本地文件——远程字体 URL(http/https)无法使用。使用前务必先将字体下载到本地文件。
- 将异步代码包装在 IIFE 中——顶层 await 会导致错误。务必使用
(async () => { ... })()模式。 - 为自定义字体禁用连字符处理——自定义字体缺少连字符处理词典,可能导致崩溃或错误地拆分单词。注册自定义字体后务必调用
Font.registerHyphenationCallback((word) => [word]);。
文件
references/google-fonts.txt——约 65 种常用 Google Fonts 的元数据及 TrueType URL。每行采用制表符分隔格式,表示一种字体变体:font name、style、category、weight、url。references/components.md——完整的组件 API 参考和支持的 CSS 属性assets/example-template.tsx——最简可运行示例,演示固定页脚、页码和不可拆分内容。开始前请先阅读,了解基本模式。注意:此处未展示所有 API——完整 API 请始终参阅文档和references/components.md。
前置条件
npm install react @react-pdf/renderer
npm install -D tsx @types/react
tsx 可通过 Node 直接运行 TypeScript + JSX 文件,无需配置——不需要 tsconfig.json。它在底层使用 esbuild,并会自动处理 JSX 转换。
核心组件
- Document:根组件(元数据、设置)
- Page:单个页面(A4、Letter 或自定义尺寸)
- View:容器组件(类似 div)
- Text:文本内容,支持嵌套以实现行内样式
- Image:嵌入图像(JPG、PNG、base64)
- Link:可点击的超链接(外部或内部)
- Note:注释
- Canvas:使用 pdfkit 方法进行自由绘图
- Svg:矢量图形(Circle、Rect、Path、Line、Polygon 等)
- StyleSheet:创建可复用样式
完整的组件属性和 CSS 属性请参阅 references/components.md。
基本示例
import React from "react";
import { Document, Page, Text, View, StyleSheet, renderToFile } from "@react-pdf/renderer";
const styles = StyleSheet.create({
page: { flexDirection: "column", backgroundColor: "#ffffff", padding: 40 },
title: { fontSize: 24, marginBottom: 20, fontWeight: "bold" },
text: { fontSize: 12, lineHeight: 1.5 },
});
const MyDocument = () => (
<Document>
<Page size="A4" style={styles.page}>
<View style={{ margin: 10, padding: 20 }}>
<Text style={styles.title}>Document Title</Text>
<Text style={styles.text}>Your content here</Text>
</View>
</Page>
</Document>
);
(async () => {
await renderToFile(<MyDocument />, "./output.pdf");
console.log("PDF saved!");
})();
运行脚本
PDF 生成脚本使用 JSX,Node 无法直接运行。请使用 tsx 执行:
npx tsx my-document.tsx
npx tsx 无需全局安装 tsx 即可运行——它会按需下载。如果将 tsx 安装为开发依赖(npm install -D tsx),运行时无需经过 npx 下载步骤,启动会更快。
渲染时务必使用异步 IIFE:
// Good
(async () => {
await renderToFile(<MyDocument />, "./output.pdf");
})();
// Bad - top-level await may fail
await renderToFile(<MyDocument />, "./output.pdf");
预览 PDF
如需直观检查生成的 PDF,请将页面转换为图像。先尝试使用 pdftoppm(通常已预装);如果不可用,再使用 Python 的 PyMuPDF。
选项 1:pdftoppm(poppler-utils)——首选,在许多环境中无需安装:
pdftoppm -png -r 200 document.pdf preview
# → preview-1.png, preview-2.png, ...
选项 2:PyMuPDF(Python)——如果 pdftoppm 不可用,则使用此备用方案:
pip install pymupdf
import fitz
doc = fitz.open("document.pdf")
for i, page in enumerate(doc):
pix = page.get_pixmap(dpi=200)
pix.save(f"page-{i+1}.png")
渲染方法
import { renderToFile, renderToBuffer } from "@react-pdf/renderer";
// To file
(async () => {
await renderToFile(<MyDocument />, "./document.pdf");
})();
// To buffer
(async () => {
const buffer = await renderToBuffer(<MyDocument />);
})();
样式
有三种方式:StyleSheet.create()、行内对象或混合数组。
const styles = StyleSheet.create({ container: { padding: 20 } });
<View style={styles.container} />
<View style={{ padding: 20 }} />
<View style={[styles.container, { marginTop: 10 }]} />
支持的单位
pt(默认,72 DPI)、in、mm、cm、%、vw、vh
常用样式属性
{
// Flexbox
flexDirection: "row", justifyContent: "space-between", alignItems: "center",
flexWrap: "wrap", gap: 10,
// Box model
margin: 10, padding: 20, width: "100%", height: 200,
// Borders
borderWidth: 1, borderColor: "#333", borderRadius: 5, borderStyle: "solid",
// Colors
backgroundColor: "#f0f0f0", color: "#000", opacity: 0.8,
// Typography
fontSize: 12, fontWeight: "bold", fontFamily: "Helvetica", fontStyle: "italic",
lineHeight: 1.5, textAlign: "center", textDecoration: "underline",
textTransform: "uppercase", letterSpacing: 1,
// Position
position: "absolute", top: 0, left: 0, right: 0, bottom: 0, zIndex: 10,
// Transforms
transform: "rotate(45deg)", transformOrigin: "center",
}
图像
本地文件最可靠。远程 URL 可能因网络或 CORS 问题而失败。
import { Image } from '@react-pdf/renderer';
<Image src="./images/photo.jpg" style={{ width: 200, height: 150 }} />
<Image src={{ data: buffer, format: 'png' }} />
**SVG 文件不能用作 Image 源。**请读取 SVG 源码,并使用 react-pdf Svg 组件重新创建。
SVG 图形
import { Svg, Circle, Rect, Path, Line, G, Defs, LinearGradient, Stop } from "@react-pdf/renderer";
<Svg width="200" height="200" viewBox="0 0 200 200">
<Defs>
<LinearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<Stop offset="0%" stopColor="#3498db" />
<Stop offset="100%" stopColor="#9b59b6" />
</LinearGradient>
</Defs>
<Circle cx="100" cy="100" r="50" fill="url(#grad1)" />
<Rect x="10" y="10" width="50" height="50" fill="#e74c3c" />
<Path d="M10,50 Q50,10 90,50" stroke="#2ecc71" strokeWidth="2" fill="none" />
</Svg>;
使用图标
读取图标库中的 SVG 源码,并将其转换为 react-pdf Svg 组件:
npm install lucide-static
import { Svg, Path, Rect } from "@react-pdf/renderer";
// Converted from lucide-static/icons/mail.svg
const MailIcon = ({ size = 12, color = "#888" }) => (
<Svg width={size} height={size} viewBox="0 0 24 24">
<Path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" stroke={color} strokeWidth={2} fill="none" />
<Rect x="2" y="4" width="20" height="16" rx="2" stroke={color} strokeWidth={2} fill="none" />
</Svg>
);
链接和导航
<Link src="https://example.com"><Text>Visit website</Text></Link>
<View id="section-1"><Text>Target</Text></View>
<Link src="#section-1"><Text>Jump to Section 1</Text></Link>
动态内容和页码
<Text render={({ pageNumber, totalPages }) => `Page ${pageNumber} of ${totalPages}`} />
固定页眉/页脚
<Page size="A4">
<View fixed style={{ position: "absolute", top: 20, left: 30, right: 30 }}>
<Text>Header</Text>
</View>
<View style={{ marginTop: 60, marginBottom: 60 }}>
<Text>Content</Text>
</View>
<Text
fixed
style={{ position: "absolute", bottom: 20, left: 30, right: 30, textAlign: "center" }}
render={({ pageNumber, totalPages }) => `Page ${pageNumber} of ${totalPages}`}
/>
</Page>
分页和换行
<View break /> // Force page break
<View wrap={false}><Text>Keep together</Text></View> // Prevent breaking inside
<Text orphans={2} widows={2}>Long text...</Text> // Orphan/widow control
<View minPresenceAhead={100}><Text>Content</Text></View> // Min space before break
自定义字体
**关键:所有字体源都必须是本地文件路径。**远程 URL 无法使用。
import { Font } from "@react-pdf/renderer";
Font.register({
family: "Roboto",
fonts: [
{ src: "./fonts/Roboto-Regular.ttf", fontWeight: "normal" },
{ src: "./fonts/Roboto-Bold.ttf", fontWeight: "bold" },
{ src: "./fonts/Roboto-Italic.ttf", fontStyle: "italic" },
],
});
// Always disable hyphenation when using custom fonts
Font.registerHyphenationCallback((word) => [word]);
内置字体:Courier、Helvetica、Times-Roman(每种字体均有 Bold、Italic/Oblique 变体)
字体粗细值:thin(100)、ultralight(200)、light(300)、normal(400)、medium(500)、semibold(600)、bold(700)、ultrabold(800)、heavy(900)
Google Fonts
使用 references/google-fonts.txt 查找字体 URL,然后下载到本地:
# Find the font URL
grep "^Roboto" skills/react-pdf/references/google-fonts.txt | grep "700" | grep "normal"
# Download
mkdir -p fonts
curl -sL "<url-from-grep>" -o fonts/Roboto-Bold.ttf
# Verify - must show "TrueType Font data"
file fonts/Roboto-Bold.ttf
如果 file 显示“HTML document”或“ASCII text”,说明下载失败。请尝试其他 URL,或在 GitHub 上搜索字体的官方仓库,查找 TTF 文件。
Emoji
如果不注册 emoji 来源,Emoji 将无法在 PDF 中呈现。安装 twemoji-emojis 即可获取本地 Twemoji PNG 资源——渲染时无需联网。
npm install twemoji-emojis
import { Font } from "@react-pdf/renderer";
Font.registerEmojiSource({
format: "png",
url: "node_modules/twemoji-emojis/vendor/72x72/",
});
然后在 Text 中直接使用 Emoji:<Text>Hello 🚀🎉</Text>
其他功能
// Canvas drawing
<Canvas style={{ width: 200, height: 200 }}
paint={(painter, w, h) => { painter.circle(w/2, h/2, 50).fill("#3498db"); }} />
// Annotation notes
<Note style={{ color: "yellow" }}>Annotation text</Note>
// Hyphenation
Font.registerHyphenationCallback((word) => [word]); // disable
// Debug mode - visualize boundaries
<View debug><Text debug>Debug text</Text></View>
// Document metadata
<Document title="My Doc" author="Author" subject="Report" language="en-US" pdfVersion="1.5" />
最佳实践
- 使用
StyleSheet.create()——定义一次样式并重复使用 - 嵌入前压缩图片;远程图片请使用
cache={true} - 测试分页符——内容的流动方式可能与预期不同
- 优先使用 flexbox,而不是绝对定位
- 使用
fixed属性,让页眉和页脚显示在每一页 - 使用
debug={true}可视化元素边界 - 将渲染过程放在 try-catch 块中
常见问题
文本溢出:<Text style={{ width: 200, maxLines: 3, textOverflow: "ellipsis" }}>...</Text>
缺少字体:将字体下载到本地,并使用本地文件路径注册。远程 URL 无法使用。
意外分页:使用 wrap={false} 将内容保持在一起,或使用 <View break /> 强制分页。