关于 integrate-uppy-transloadit-s3-uploading-to-nextjs
这项技能指导在 Next.js 应用中接入 Uppy 和 Transloadit 上传,包括服务端签名和浏览器上传面板。还介绍了可选的 Transloadit 模板,用于调整图片尺寸并导出到 S3。
主要功能
- 01服务端签署 Assembly 选项
- 02Uppy Dashboard 上传界面
- 03集成 Transloadit 插件
- 04可选图片缩放和 S3 导出
- 05将 Transloadit 密钥保留在服务端
适用场景
- 01为 Next.js 应用添加文件上传
- 02签署上传选项且不暴露密钥
- 03缩放上传图片并导出到 S3
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ transloadit/ skills --skill integrate-uppy-transloadit-s3-uploading-to-nextjs安装后在 Claude Code 中输入/integrate-uppy-transloadit-s3-uploading-to-nextjs确认已加载。
详细介绍
输入
- 必需的环境变量(仅服务端):
TRANSLOADIT_KEY、TRANSLOADIT_SECRET - 可选的环境变量:
TRANSLOADIT_TEMPLATE_ID(创建模板后建议设置)
本地开发时,将这些变量写入 .env.local。切勿向浏览器公开 TRANSLOADIT_SECRET。
安装
npm i @transloadit/utils @uppy/core @uppy/dashboard @uppy/transloadit
实现(推荐路径)
选择根目录:
- 如果项目中有
src/app,使用src/app/... - 否则使用
app/...
1) 服务端:向浏览器返回已签名的 Assembly 选项
创建 app/api/transloadit/assembly-options/route.ts(如果使用 src/,则创建 src/app/api/transloadit/assembly-options/route.ts):
import { NextResponse } from 'next/server'
import { signParamsSync } from '@transloadit/utils/node'
export const runtime = 'nodejs'
function reqEnv(name: string): string {
const v = process.env[name]
if (!v) throw new Error(`Missing required env var: ${name}`)
return v
}
function formatExpiresUtc(minutesFromNow: number): string {
const ms = Date.now() + minutesFromNow * 60_000
return new Date(ms).toISOString().replace(/\.\d{3}Z$/, 'Z')
}
export async function POST() {
try {
const authKey = reqEnv('TRANSLOADIT_KEY')
const authSecret = reqEnv('TRANSLOADIT_SECRET')
const templateId = process.env.TRANSLOADIT_TEMPLATE_ID
const params: Record<string, unknown> = {
auth: { key: authKey, expires: formatExpiresUtc(30) },
}
if (templateId) {
params.template_id = templateId
} else {
// Minimal "known good" steps (works without pre-creating a template).
params.steps = {
resized: {
robot: '/image/resize',
use: ':original',
width: 320,
},
}
}
const paramsString = JSON.stringify(params)
const signature = signParamsSync(paramsString, authSecret)
// Uppy expects `{ params: <string|object>, signature: <string> }`.
return NextResponse.json({ params: paramsString, signature })
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error'
return NextResponse.json({ error: message }, { status: 500 })
}
}
2) 客户端:挂载 Uppy Dashboard 和 Transloadit 插件
添加 CSS(对于 App Router,请在根布局中添加):
import '@uppy/core/css/style.min.css'
import '@uppy/dashboard/css/style.min.css'
创建一个客户端组件,例如 app/upload-demo.tsx:
'use client'
import { useEffect, useMemo, useRef, useState } from 'react'
import Uppy from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import Transloadit, { type AssemblyOptions } from '@uppy/transloadit'
export default function UploadDemo() {
const dashboardEl = useRef<HTMLDivElement | null>(null)
const [results, setResults] = useState<unknown>(null)
const [uploadPct, setUploadPct] = useState<number>(0)
const uppy = useMemo(() => {
const instance = new Uppy({
autoProceed: true,
restrictions: { maxNumberOfFiles: 1 },
})
instance.use(Transloadit, {
waitForEncoding: true,
alwaysRunAssembly: true,
assemblyOptions: async (): Promise<AssemblyOptions> => {
const res = await fetch('/api/transloadit/assembly-options', { method: 'POST' })
if (!res.ok) throw new Error(`Failed to get assembly options: ${res.status}`)
return (await res.json()) as AssemblyOptions
},
})
return instance
}, [])
useEffect(() => {
if (!dashboardEl.current) return
uppy.use(Dashboard, {
target: dashboardEl.current,
inline: true,
proudlyDisplayPoweredByUppy: false,
hideUploadButton: true,
hideProgressDetails: false,
height: 350,
})
const onResult = (stepName: string, result: unknown) =>
setResults((prev: unknown) => {
const base: Record<string, unknown> =
typeof prev === 'object' && prev !== null ? { ...(prev as Record<string, unknown>) } : {}
const existing = base[stepName]
base[stepName] = Array.isArray(existing) ? existing.concat([result]) : [result]
return base
})
const onUploadProgress = (_file: unknown, progress: { bytesUploaded: number; bytesTotal: number | null }) => {
if (!progress?.bytesTotal) return
setUploadPct(Math.round((progress.bytesUploaded / progress.bytesTotal) * 100))
}
uppy.on('transloadit:result', onResult)
uppy.on('upload-progress', onUploadProgress)
return () => {
uppy.off('transloadit:result', onResult)
uppy.off('upload-progress', onUploadProgress)
uppy.getPlugin('Dashboard')?.uninstall()
uppy.destroy()
}
}, [uppy])
return (
<section>
<div ref={dashboardEl} />
<div style={{ marginTop: 12 }}>{uploadPct}%</div>
<pre style={{ marginTop: 12 }}>{results ? JSON.stringify(results, null, 2) : '(no results yet)'}</pre>
</section>
)
}
可选:/s3/store 导出
推荐做法:在 Transloadit 中创建模板凭据(这样就无需在任何地方提供 AWS 密钥),并在 /s3/store 中引用它们。
示例步骤:
{
"resized": { "robot": "/image/resize", "use": ":original", "width": 320 },
"exported": {
"robot": "/s3/store",
"use": "resized",
"credentials": "YOUR_TRANSLOADIT_TEMPLATE_CREDENTIALS_NAME",
"path": "uppy-nextjs/${unique_prefix}/${file.url_name}",
"acl": "private"
}
}
如果你有意让对象公开,请将 "acl" 改为 "public-read"(并考虑存储桶策略、访问日志和数据保留)。
然后创建模板并设置 TRANSLOADIT_TEMPLATE_ID:
npx -y @transloadit/node templates create uppy-nextjs-resize-to-s3 ./steps.json -j
参考资料(内部)
- 可用的参考实现:
https://github.com/transloadit/skills/tree/main/scenarios/integrate-uppy-transloadit-s3-uploading-to-nextjs - 经验证的步骤 JSON:
https://github.com/transloadit/skills/blob/main/scenarios/integrate-uppy-transloadit-s3-uploading-to-nextjs/transloadit/steps/resize-only.json、https://github.com/transloadit/skills/blob/main/scenarios/integrate-uppy-transloadit-s3-uploading-to-nextjs/transloadit/steps/resize-to-s3.json
已测试版本(确切版本请参阅该场景的锁定文件):
- Next.js 16.1.6(App Router)
- React 19.2.3
- @transloadit/utils 4.3.0(Assembly 签名)
- @uppy/core 5.2.0、@uppy/dashboard 5.1.1、@uppy/transloadit 5.5.0
评论
支持 Markdown