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

如何基于S3对象名通过专属URL展示指定PDF?

解决方案:NextJS 13.4 App Router 结合私有S3桶展示PDF

针对你的需求,这里有三个可行的方案,从简单到进阶,你可以根据应用规模选择:

方案一:Server Component实时生成短期预签名URL

预签名URL过期的问题可以通过用户访问页面时实时生成解决,每次加载页面都会生成新的有效URL,完全不用提前生成或担心过期。而且App Router的Server Component运行在服务器端,不会把AWS凭证暴露给前端,安全性有保障。

实现步骤:

  1. 安装AWS SDK v3依赖:
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
  1. 创建动态路由页面 app/exams/[subject]/[filename]/page.tsx:
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

export default async function ExamPage({ params }: { params: { subject: string; filename: string } }) {
  const { subject, filename } = params;
  // 初始化S3客户端(凭证从环境变量读取,禁止硬编码)
  const s3Client = new S3Client({
    region: "你的AWS区域",
    credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    },
  });
  // 构造获取S3对象的命令
  const getObjectCmd = new GetObjectCommand({
    Bucket: "你的存储桶名称",
    Key: `${subject}/${filename}`,
  });
  // 生成15分钟有效期的预签名URL(足够用户正常查看PDF)
  const signedUrl = await getSignedUrl(s3Client, getObjectCmd, { expiresIn: 900 });

  return (
    <div style={{ width: "100%", height: "100vh" }}>
      <iframe
        src={signedUrl}
        width="100%"
        height="100%"
        title={`${subject} - ${filename}`}
        frameBorder="0"
      />
    </div>
  );
}
  • 注意:AWS区域、存储桶名、凭证通过.env.local配置,绝对不要写在代码里。

方案二:API Route作为代理转发S3文件

如果不想让用户接触到任何S3相关的URL,可以用NextJS的API Route做代理:用户访问页面时,前端请求你的API路由,API路由从S3拉取文件内容再返回给前端,完全隐藏S3的存在,也不用处理预签名过期问题。

实现步骤:

  1. 同样先安装AWS SDK v3依赖。
  2. 创建API路由 app/api/exams/[subject]/[filename]/route.ts:
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { NextResponse } from "next/server";

export async function GET(
  _request: Request,
  { params }: { params: { subject: string; filename: string } }
) {
  const { subject, filename } = params;
  const s3Client = new S3Client({
    region: "你的AWS区域",
    credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    },
  });

  try {
    const getObjectCmd = new GetObjectCommand({
      Bucket: "你的存储桶名称",
      Key: `${subject}/${filename}`,
    });
    const s3Response = await s3Client.send(getObjectCmd);
    // 将S3的文件流返回给前端,设置正确的Content-Type让浏览器识别为PDF
    return new NextResponse(s3Response.Body as ReadableStream, {
      headers: {
        "Content-Type": "application/pdf",
        "Content-Disposition": `inline; filename="${filename}"`,
      },
    });
  } catch (error) {
    // 处理文件不存在或权限错误
    return NextResponse.json({ error: "无法获取文件" }, { status: 404 });
  }
}
  1. 在页面组件里使用API路由的URL:
export default function ExamPage({ params }: { params: { subject: string; filename: string } }) {
  const { subject, filename } = params;
  // 对文件名编码,避免空格或特殊字符导致的URL错误
  const proxyUrl = `/api/exams/${subject}/${encodeURIComponent(filename)}`;

  return (
    <div style={{ width: "100%", height: "100vh" }}>
      <iframe
        src={proxyUrl}
        width="100%"
        height="100%"
        title={`${subject} - ${filename}`}
        frameBorder="0"
      />
    </div>
  );
}
  • 缺点:服务器需要转发PDF流量,若文件很大或访问量高会占用服务器带宽,适合中小规模应用。

方案三:CloudFront + S3 OAC(进阶大流量场景)

如果应用有大量PDF文件或高访问量,推荐用CloudFront做CDN,配合Origin Access Control(OAC)让CloudFront能访问私有S3桶,同时利用CloudFront的缓存和访问控制能力。

核心步骤:

  1. 创建CloudFront分发,源设置为你的S3桶,启用Origin Access Control(OAC),确保只有CloudFront能访问S3,桶保持私有。
  2. 配置CloudFront的行为,允许GET请求,设置合适的缓存策略(比如缓存PDF文件1天)。
  3. 若需控制用户访问权限,可使用CloudFront的Signed URLs/Cookies,在NextJS服务器端生成签名URL(有效期可设置更长,或结合用户身份验证)。
  4. 在NextJS页面里生成CloudFront的签名URL,嵌入到iframe中。
  • 优点:利用CDN缓存减轻S3和NextJS服务器的压力,适合大流量、大文件场景,安全性更高。

方案选择建议

  • 小型应用/快速实现:选方案一,代码简单,无需额外配置。
  • 希望完全隐藏S3细节:选方案二。
  • 大流量/大文件场景:选方案三。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 00:50:06