解决方案
简直不敢相信居然没有人写过一篇关于如何在 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=xxxxR2客户端配置
            
            
              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...
