如何在 Next.js 13的 app/ 目录中使用 Cloudflare R2 存储

解决方案

简直不敢相信居然没有人写过一篇关于如何在 Next.js 13 的 app/ 目录中使用 Cloudflare R2 的指南,所以我就自己写了一篇。

Cloudflare CORS 策略配置

css 复制代码
[  {    "AllowedOrigins": [      "http://localhost:3000"    ],
    "AllowedMethods": [
      "GET", 
      "PUT",
      "POST",   
      "HEAD",
      "DELETE"
    ],
    "AllowedHeaders": [
      "*"
    ],
    "ExposeHeaders": [],
    "MaxAgeSeconds": 3000    
  }
]

所需的依赖

bash 复制代码
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

环境变量配置

ini 复制代码
// 在 .env.local 中配置
// 从 Cloudflare R2 控制面板生成 token,可参考官方的 Youtube 视频教程

R2_ACCESS_KEY_ID=xxxx  
R2_SECRET_ACCESS_KEY=xxxx
R2_BUCKET_NAME=xxxx
R2_ACCOUNT_ID=xxxx

R2客户端配置

javascript 复制代码
// lib/r2.ts

import { S3Client } from '@aws-sdk/client-s3'

export const r2 = new S3Client({
  region: 'auto',
  endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID || '',
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY || '',
  },
})

上传组件实现

javascript 复制代码
// app/(site)/admin/page.tsx  
'use client'

import React from 'react'
import { DocumentIcon } from '@heroicons/react/24/solid'

const Admin = () => {

  // 文件状态管理
  const [file, setFile] = React.useState<File>()

  // 文件改变事件处理
  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files) {
      const currentFile = event.target.files[0]
      setFile(currentFile) 
    }
  }

  // 文件上传逻辑
  const handleUpload = async () => {
    if (!file) return
    
    const formData = new FormData()
    formData.append('file', file)

    // 1. 生成上传URL
    const response = await fetch('/api/upload', {
      method: 'POST',
    })
    const { url } = await response.json()
    
    // 2. 使用上传URL上传文件 
    await fetch(url, {  
      method: 'PUT',
      body: formData,
    })
  }

  return (
    <div className="bg-slate-900 text-white">
      {/* 示例UI */}

      <button onClick={handleUpload}>
        上传
      </button>
    </div>
  )  
}

export default Admin

上传API路由实现

javascript 复制代码
// app/api/upload/route.ts  

import { NextResponse } from 'next/server'
import chalk from 'chalk'
import { PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'

import { r2 } from '@/lib/r2'

export async function POST(request: Request) {

  try {
    
    console.log(chalk.yellow(`正在生成上传URL!`))  

    // 生成上传URL
    const signedUrl = await getSignedUrl(
      r2,
      new PutObjectCommand({
        Bucket: process.env.R2_BUCKET_NAME, 
        Key: `filename.pdf`,    
      }),
      { expiresIn: 60 } 
    )

    console.log(chalk.green(`上传URL生成成功!`))
    
    // 返回上传URL
    return NextResponse.json({ url: signedUrl })

  } catch (err) {
    console.log('出错了')
  } 

}

总的来说,主要就是配置好 R2 客户端,然后生成上传 URL,前端使用该 URL 上传文件。同样的方法也可以实现下载文件。

实现文件下载

javascript 复制代码
// app/api/download/route.ts

import { GetObjectCommand } from '@aws-sdk/client-s3'
import chalk from 'chalk'

import { r2 } from '@/lib/r2'

export async function GET() {
  try {

    console.log(chalk.yellow(`从 R2 检索 PDF文件!`))  

    // 从 R2 获取 PDF 文件
    const pdf = await r2.send(
      new GetObjectCommand({
        Bucket: process.env.R2_BUCKET_NAME,
        Key: 'filename.pdf', 
      })
    )

    if (!pdf) {
      throw new Error('未找到 PDF 文件。')
    }

    // 返回 PDF 文件流
    return new Response(pdf.Body?.transformToWebStream(), {   
      headers: {
        'Content-Type': 'application/pdf',
      },
    })

  } catch (err) {
    console.log('出错:', err) 
  }
}

前端实现下载

javascript 复制代码
// app/(site)/admin/page.tsx

const handleDownload = async () => {

  // 调用下载 API 
  const response = await fetch('/api/download')
  
  // 获取文件 Blob
  const blob = await response.blob()
  
  // 生成临时 URL
  const fileURL = window.URL.createObjectURL(blob) 
  
  // 触发下载
  let anchor = document.createElement('a')
  anchor.href = fileURL
  anchor.download = 'filename.pdf'
  anchor.click()

}

// 添加下载按钮
<button onClick={handleDownload}>
  下载
</button>

主要就是通过 API 从 R2 获取文件数据流,然后前端通过各种方式把文件流转换并触发下载。

最后

相关推荐
10年前端老司机3 小时前
React无限级菜单:一个项目带你突破技术瓶颈
前端·javascript·react.js
阿芯爱编程8 小时前
2025前端面试题
前端·面试
前端小趴菜059 小时前
React - createPortal
前端·vue.js·react.js
晓13139 小时前
JavaScript加强篇——第四章 日期对象与DOM节点(基础)
开发语言·前端·javascript
菜包eo9 小时前
如何设置直播间的观看门槛,让直播间安全有效地运行?
前端·安全·音视频
烛阴10 小时前
JavaScript函数参数完全指南:从基础到高级技巧,一网打尽!
前端·javascript
chao_78911 小时前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼11 小时前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript
三原11 小时前
7000块帮朋友做了2个小程序加一个后台管理系统,值不值?
前端·vue.js·微信小程序
popoxf12 小时前
在新版本的微信开发者工具中使用npm包
前端·npm·node.js