使用 Next.js、NodeJS 和 puppeteer 将 React 应用程序转换为 PDF

2025-05-25

使用 Next.js、NodeJS 和 puppeteer 将 React 应用程序转换为 PDF

大家好,首先我想说的是:这还不是一个可以投入生产的实现。我们可以做一些改进,让它更符合生产环境。如果大家感兴趣的话,我可以再发一篇后续文章。

一个月前,我用 Next.js 和 Tailwindcss 重新制作了我的简历。说实话,我讨厌用 Word 或 Pages 制作简历,总是跟空格之类的问题作斗争。

要知道,React 或 Next.js 对于创建简历来说可能有点过度,但如果您必须在现有的应用程序中生成发票,这种技术就会派上用场。

哦,为什么是 Next.js?同样的概念也适用于 NodeJS 和 CRA,但 Next.js 已经成为我编写 React 应用的首选样板,因为它提供了许多开箱即用的功能。

我使用此技术创建并导出的网页简历:这是生成的 PDF 的链接
图片描述

为什么?

在我最初搜索如何生成 PDF 的过程中,你很快就会发现它比你想象的要难得多。可以使用类似pdfkitPDF-LIB的库来创建 PDF,如下所示:



// pdfkit

doc
  .font('fonts/Inter.ttf')
  .fontSize(20)
  .text('Hello PDF', 100, 100)

doc
  .moveTo(100, 150)
  .lineTo(100, 250)
  .lineTo(200, 250)
  .fill('#FF3300')


Enter fullscreen mode Exit fullscreen mode

我不知道你是怎么想的,但是我不想以这种方式建立我的简历。

另一个常见的伎俩是将网页转换成图片,然后再转换成PDF。问题是,这些图片PDF在放大时无法缩放,也无法复制文本、点击链接等。

还有“打印为 PDF”的技巧。这种方法的缺点是,每次要保存时,最终用户都必须手动打开页面,点击打印,然后再“打印为 PDF”。虽然如果你用 HTML 和 CSS 设计简历,这种方法还行,但如果你开发的工具需要最终用户导出像发票这样的 PDF 文件,那么这种方法就会变得非常繁琐。

按照本指南,你将学习如何使用 Puppeteer 将你的 React、CSS 页面转换为 PDF!这里,你将找到包含代码和生成的PDF 的仓库。
图片描述

要求

确保已NodeJS安装,我使用的是16Next.js版本。建议对 及其 API 路由有基本的了解。

入门

让我们首先通过运行以下命令创建一个新的 Next.js 项目:



npx create-next-app --ts --use-npm


Enter fullscreen mode Exit fullscreen mode

一旦项目设置完成,让我们安装 puppeteer:



npm install puppeteer 


Enter fullscreen mode Exit fullscreen mode

现在启动开发服务器npm run dev并清除里面的标准样板代码pages/index.tsx等。

布局

我们首先创建 Page 组件,它将提供 A4 大小的容器。这只是一个简单的组件,它渲染一个页面,div并应用样式来模拟 A4 大小的纸张。



// components/Page.tsx
import styles from '../styles/Page.module.css'

type Props = {
  children: React.ReactNode
}

const Page = ({ children }: Props) => (
  <div className={styles.page}>
      {children}
  </div>
)

export default Page


Enter fullscreen mode Exit fullscreen mode

在我们开始Page组件样式之前,让我们先应用一些全局样式:



/* styles/global.css */

html {
  -webkit-print-color-adjust: exact; /* This makes sure that the PDF is rendered exactly like our layout. */
}

html,
body {
  padding: 0;
  margin: 0;
  background: #f1f5f9; /* Light gray background */
  width: 100%;
  height: 100%;
}

/* Next.js mounting point. Create a full width/height container for our page. */
#__next {
  height: 100vh;
  display: grid;
}

* {
  box-sizing: border-box;
}

/* Important to keep absolute as you don't want this to be rendered by the PDF. */
.downloadBtn {
  position: absolute;
  top: 0;
}


Enter fullscreen mode Exit fullscreen mode

对于我们的页面样式:



/* styles/Page.module.css */

.page {
  margin: auto; /* centers element within parent container */
  background: white; /* ofcourse we want our pdf background to be white */
  position: relative; /* for child elements that need absolute positioning */

  /* below is the width/height for an A4 sized sheet. For other standards lookup 
     the dimensios and apply those. */
  width: 210mm;
  height: 297mm;

  padding: 32px;
  /* optional: Add drop shadow for floating paper effect. */
  filter: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1));
}

@page {
  size: A4;
  margin: 0;
}


Enter fullscreen mode Exit fullscreen mode

现在让我们将 Page 组件引入到我们的主页中。



// pages/index.tsx
import type { NextPage } from 'next'
import Page from '../components/Page'

const Home: NextPage = () => {
  return (
  <>
    <Page>
      <h1>Generated PDF</h1>
      <p>This text will be in the PDF!</p>
    </Page>
  </>
  )
}

export default Home


Enter fullscreen mode Exit fullscreen mode

如果一切顺利的话,它应该看起来像:
PDF 示例

现在您已经拥有了开始生成 PDF 的完美基础,让我们开始吧!

使用 Puppeteer 生成 PDF

对于不熟悉 puppeteer 的人,请参阅其 Github 页面:

Puppeteer 是一个 Node 库,它提供了通过DevTools 协议控制 Chrome 或 Chromium 的高级 API。Puppeteer默认以无头模式运行,但可以配置为运行完整(非无头)的 Chrome 或 Chromium。

如上所述,手动为最终用户生成每张发票“打印为 PDF”可能会相当麻烦。如果我们让 Puppeteer 在后台帮我们完成这项工作,然后将结果返回,会怎么样呢?

让我们从创建 API 路由开始:



// pages/api/pdf.ts
import { NextApiHandler } from 'next'
import puppeteer from 'puppeteer'

const Handler: NextApiHandler = async (req, res) => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.goto('http://localhost:3000')
  await page.emulateMediaType('screen')

  const pdfBuffer = await page.pdf({ format: 'A4' })

  res.send(pdfBuffer)

  await browser.close()
}


Enter fullscreen mode Exit fullscreen mode
简要总结一下:

我们创建了一个名为 的 API 路由pages/api/pdf.ts,并在其中导入puppeteer。当调用 时http://localhost:3000/api/pdf,我们会启动一个 Puppeteer 实例,打开一个新页面并将该实例定向到我们的应用。
我们将媒体模拟模式设置为 ,screen并启动 PDF 生成过程。
的输出pdf()是一个缓冲区,我们将其返回给用户。
然后,我们关闭创建的浏览器实例并完成我们的处理程序。

尝试一下!

您可以访问 进行测试http://localhost:3000/api/pdf。现在您应该可以看到包含文本/组件的 PDF 文件!

为了使这更容易一些,让我们包含一个可以为我们执行此操作的链接:



<>
  <a href="/api/pdf" download="generated_pdf.pdf" className="downloadBtn">Download PDF</a>
  <Page>
    <h1>Generated PDF</h1>
    <p>As you can see you can scroll without issues and select text.</p>
  </Page>
<>


Enter fullscreen mode Exit fullscreen mode


.downloadBtn {
  position: absolute;
  top: 10px;
  left: 10px;
}


Enter fullscreen mode Exit fullscreen mode

对于下载链接,我们指定了/api/pdf路径。加上download="FILENAME.pdf",我们现在有一个可点击的下载链接,可以帮我们下载 PDF。

既然我们已经这样做了,不妨尝试一下另一个页面!



<>
<a href="/api/pdf" download="generated_pdf.pdf" className="downloadBtn">Download PDF</a>
<Page>
<h1>Generated PDF</h1>
<p>As you can see you can scroll without issues and select text.</p>
</Page>
<Page>
<h1>Page 2</h1>
<p>As you can see you can scroll without issues and select text.</p>
</Page>
</>

Enter fullscreen mode Exit fullscreen mode




限制

我再强调一下:这还不能用于生产环境。在 Page 组件周围添加元素会导致 PDF 输出错误。这是因为布局不再是 A4 大小。
我在其他项目中通过使用样式和条件解决了这个问题,最终看起来仍然非常优雅简洁。

如果您有兴趣跟进、验证生产实施情况或有任何疑问,请告诉我!

文章来源:https://dev.to/jordykoppen/turning-react-apps-into-pdfs-with-nextjs-nodejs-and-puppeteer-mfi
PREV
免费部署个人网站/PHP+MySQL Web 应用的 6 种方法
NEXT
4 本非计算机书籍让我成为一名优秀的软件开发人员