integrate-uppy-transloadit-s3-uploading-to-nextjs について
Next.js アプリに Uppy と Transloadit のアップロード機能を追加するスキルです。サーバー側の署名とブラウザーのアップロード画面を扱い、画像のリサイズと S3 への保存も任意で設定できます。
主な機能
- 01サーバー側での Assembly オプション署名
- 02Uppy Dashboard のアップロード画面
- 03Transloadit プラグインの連携
- 04任意の画像リサイズと S3 エクスポート
- 05Transloadit のシークレットをサーバー側に保持
活用シーン
- 01Next.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 - 実績のある steps 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