SKILLS/integrate-uppy-transloadit-s3-uploading-to-nextjs/SKILL.md
Add Uppy Dashboard + Transloadit uploads to a Next.js (App Router) app, with server-side signature generation and optional /s3/store export.
npx skillsauth add pinkpixel-dev/skills-collection-2 integrate-uppy-transloadit-s3-uploading-to-nextjsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
TRANSLOADIT_KEY, TRANSLOADIT_SECRETTRANSLOADIT_TEMPLATE_ID (recommended once you create a template)For local dev, put these in .env.local. Never expose TRANSLOADIT_SECRET to the browser.
npm i @transloadit/utils @uppy/core @uppy/dashboard @uppy/transloadit
Pick the root:
src/app, use src/app/...app/...Create app/api/transloadit/assembly-options/route.ts (or src/app/api/transloadit/assembly-options/route.ts if you use src/):
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 })
}
}
Add the CSS (for App Router, do it in your root layout):
import '@uppy/core/css/style.min.css'
import '@uppy/dashboard/css/style.min.css'
Create a client component like 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>
)
}
Recommended approach: create Template Credentials in Transloadit (so you don’t ship AWS keys anywhere) and reference them in /s3/store.
Example steps:
{
"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"
}
}
If you intentionally want public objects, change "acl" to "public-read" (and consider bucket policy, access logs, and data retention).
Then create a template and set 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-nextjshttps://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.jsonTested with (see the scenario lockfile for the exact versions):
tools
Perform lateral movement across Windows networks using WMI-based remote execution techniques including Impacket wmiexec.py, CrackMapExec, and native WMI commands for stealthy post-exploitation during red team engagements.
data-ai
Detects lateral movement techniques including Pass-the-Hash, PsExec, WMI execution, RDP pivoting, and SMB-based spreading using SIEM correlation of Windows event logs, network flow data, and endpoint telemetry mapped to MITRE ATT&CK Lateral Movement (TA0008) techniques.
tools
Kubernetes penetration testing systematically evaluates cluster security by simulating attacker techniques against the API server, kubelet, etcd, pods, RBAC, network policies, and secrets. Using tools
testing
Assess the security posture of Kubernetes etcd clusters by evaluating encryption at rest, TLS configuration, access controls, backup encryption, and network isolation.