解决方案
简直不敢相信居然没有人写过一篇关于如何在 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 获取文件数据流,然后前端通过各种方式把文件流转换并触发下载。
最后
- 这里是想一个拥有自己的金字塔的大厂程序员
- 欢迎关注我的Twitter: twitter.com/benshandebi...
- 这里是最近写的一个stable video online小网站,欢迎访问:stablevideoonline.com/
- 这篇文章是reddit里发现的,极其好用、高效,所以搬运一下:www.reddit.com/r/nextjs/co...