You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React + Next.js SSR环境下点击按钮下载服务端PDF文件的最优方案是什么

Next.js SSR项目实现服务端PDF下载的最佳实践

以下方案覆盖公开文件、私有鉴权文件两种主流场景,可根据自身需求选择:

场景1:PDF为公开无权限要求的文件

这种情况直接用原生标签实现即可,零额外开发成本:

  • 实现步骤:
    • 把PDF文件放入项目public目录下,例如存放路径为public/assets/report.pdf
    • 前端按钮直接嵌套带download属性的a标签:
    const DownloadPdfBtn = () => {
      return (
        <a href="/assets/report.pdf" download="自定义导出文件名.pdf">
          <button type="button">下载PDF</button>
        </a>
      )
    }
    
  • 优势:无需服务端代码,SSR/CSR模式下均生效,不会触发Next.js路由拦截,性能最优。

场景2:PDF为私有文件,需要鉴权后才可下载(生产环境常用场景)

这种情况不能把文件放在公开的public目录,需要走API接口做权限校验后返回文件流,分两种路由模式的实现:

Pages Router 实现

  1. 先编写服务端API接口,路径为pages/api/download-pdf.js:
import fs from 'fs'
import path from 'path'

export default function handler(req, res) {
  // 此处替换为项目自身的鉴权逻辑,例如校验请求头的token、用户权限
  const token = req.headers.authorization?.split(' ')[1]
  if (!token || !validateUserToken(token)) {
    return res.status(401).send('无下载权限')
  }

  // 服务端私有PDF存放路径,不要放在public目录
  const filePath = path.join(process.cwd(), 'private-storage', 'user-report.pdf')
  const fileBuffer = fs.readFileSync(filePath)

  // 设置响应头强制触发下载
  res.setHeader('Content-Type', 'application/pdf')
  res.setHeader('Content-Disposition', `attachment; filename="用户自定义文件名.pdf"`)
  res.send(fileBuffer)
}
  1. 前端按钮调用逻辑:
const DownloadPdfBtn = () => {
  const handleDownload = async () => {
    try {
      const res = await fetch('/api/download-pdf', {
        headers: {
          // 携带鉴权token
          Authorization: `Bearer ${localStorage.getItem('user_token')}`
        }
      })
      if (!res.ok) throw new Error('下载请求失败')
      
      // 把响应流转为blob对象触发下载
      const blob = await res.blob()
      const url = window.URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = url
      a.download = '自定义导出文件名.pdf'
      a.click()
      // 释放内存避免泄漏
      window.URL.revokeObjectURL(url)
    } catch (err) {
      console.error('下载出错:', err)
    }
  }

  return <button onClick={handleDownload}>下载PDF</button>
}

App Router 实现

  1. API路由写在app/api/download-pdf/route.js:
import fs from 'fs/promises'
import path from 'path'
import { NextResponse } from 'next/server'

export async function GET(request) {
  // 替换为自身项目的鉴权逻辑
  const token = request.headers.get('authorization')?.split(' ')[1]
  if (!token || !validateUserToken(token)) {
    return NextResponse.json({ error: '无下载权限' }, { status: 401 })
  }

  const filePath = path.join(process.cwd(), 'private-storage', 'user-report.pdf')
  const fileBuffer = await fs.readFile(filePath)

  return new NextResponse(fileBuffer, {
    headers: {
      'Content-Type': 'application/pdf',
      'Content-Disposition': `attachment; filename="自定义导出文件名.pdf"`
    }
  })
}
  1. 前端调用逻辑和Pages Router完全一致,直接复用上面的组件代码即可。

注意事项:

  • 若PDF文件体积超过10M,不要一次性读入内存,改成流式读取返回,避免服务端OOM
  • 生产环境如果PDF存在云存储(OSS/COS等),建议服务端直接生成带过期时间的签名下载地址返回给前端,无需走服务端中转文件流,性能更高
  • 无需引入第三方下载库,原生API已经可以覆盖所有场景,可减少项目包体积

内容的提问来源于stack exchange,提问作者Forshank

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 08:18:03