@uploadbox/nextjs
v0.7.0
Published
Next.js route handler for Uploadbox — presigned URL uploads with your own S3 bucket
Maintainers
Readme
@uploadbox/nextjs
Next.js route handler for Uploadbox — presigned URL uploads with your own S3 bucket.
Installation
npm i @uploadbox/nextjs @uploadbox/corePeer dependency: Next.js 14, 15, or 16.
Quick Start
1. Define your file router
// src/lib/uploadbox.ts
import { f } from "@uploadbox/core";
export const router = {
imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 5 } })
.middleware(async ({ auth }) => {
return { userId: auth?.userId ?? auth?.apiKeyId };
})
.onUploadComplete(async ({ file, metadata }) => {
console.log("Upload complete:", file.url);
return { fileId: file.key };
}),
};
export type AppRouter = typeof router;2. Create the route handler
// src/app/api/uploadbox/route.ts
import { createRouteHandler } from "@uploadbox/nextjs";
import { router } from "@/lib/uploadbox";
export const { GET, POST } = createRouteHandler({ router });S3 credentials are read from environment variables by default (UPLOADBOX_S3_BUCKET, UPLOADBOX_AWS_REGION, UPLOADBOX_AWS_ACCESS_KEY_ID, UPLOADBOX_AWS_SECRET_ACCESS_KEY), or pass a config object explicitly.
3. Add the React components
See @uploadbox/react for the client-side integration.
Lifecycle Hooks
Lifecycle hooks let you integrate authentication, quotas, and database tracking without coupling your upload logic to a specific database.
export const { GET, POST } = createRouteHandler({
router,
hooks: {
// API key auth (hosted mode):
onAuthenticate: async (req) => {
const token = req.headers.get("authorization");
const user = await verifyToken(token);
return { apiKeyId: user.id, apiKeyName: user.name };
},
// Session auth (self-hosted mode):
// onAuthenticate: async (req) => {
// const session = await getSession(req);
// if (!session) return undefined;
// return { userId: session.id, userName: session.name };
// },
onQuotaCheck: async ({ auth, totalSize, fileCount }) => {
const usage = await getStorageUsage(auth.apiKeyId ?? auth.userId);
if (usage + totalSize > MAX_STORAGE) {
throw UploadboxError.quotaExceeded();
}
},
onUploadStarted: async (events) => {
await db.insert(uploads).values(events.map(toRecord));
},
onUploadCompleted: async (event) => {
await db.update(uploads)
.set({ status: "completed" })
.where(eq(uploads.key, event.file.key));
},
onUploadFailed: async (event) => {
await db.update(uploads)
.set({ status: "failed", error: event.error })
.where(eq(uploads.key, event.fileKey));
},
onFileVerified: async (fileKey) => {
// Fallback lookup when in-memory state is lost (e.g., multi-instance deploys)
return db.query.uploads.findFirst({ where: eq(uploads.key, fileKey) });
},
},
});Available Hooks
| Hook | When | Purpose |
|------|------|---------|
| onAuthenticate | Every request | Return AuthContext or undefined for anonymous. Use apiKeyId/apiKeyName for hosted/API-key auth; use userId/userName for self-hosted session auth |
| onQuotaCheck | Before presigning | Throw UploadboxError.quotaExceeded() to deny |
| onUploadStarted | After presigned URLs generated | Track pending uploads |
| onFileVerified | On complete, if in-memory miss | Fallback lookup for file data |
| onUploadCompleted | After onUploadComplete succeeds | Update DB, trigger webhooks |
| onUploadFailed | On upload failure | Log errors, clean up |
| onMultipartStarted | Multipart upload created | Track large uploads |
| onMultipartCompleted | Multipart upload finished | Update status |
| onMultipartAborted | Multipart upload cancelled | Clean up |
| onResolveConfig | Every request | Per-tenant S3 config (BYOB) |
Rate Limiting
Built-in sliding window rate limiter, no external dependencies:
export const { GET, POST } = createRouteHandler({
router,
rateLimit: {
requestsPerMinute: 60,
uploadsPerHour: 100,
},
});Limits are applied per API key or client IP.
Processing Pipeline
Run post-upload processing hooks (image resize, virus scan, etc.):
import { createImageResizeHook } from "@uploadbox/core/hooks/image-resize";
import { createVirusScanHook } from "@uploadbox/core/hooks/virus-scan";
export const { GET, POST } = createRouteHandler({
router,
processing: {
hooks: [
createImageResizeHook({ maxWidth: 1920, format: "webp" }),
createVirusScanHook({ clamavUrl: "http://localhost:3310" }),
],
mode: "sequential", // or "parallel"
continueOnError: true,
},
});Or use runProcessingPipeline directly:
import { runProcessingPipeline } from "@uploadbox/nextjs";
const results = await runProcessingPipeline(pipelineConfig, file, metadata, s3Client, config);Hosted Mode
For the Uploadbox hosted platform, use createHostedHandler which adds platform-integrated auth, quotas, and event tracking:
import { createHostedHandler } from "@uploadbox/nextjs";
export const { GET, POST } = createHostedHandler({
router,
platform: {
platformUrl: "https://uploadbox.dev",
projectId: "proj_abc123",
},
});Configuration
Full createRouteHandler options:
createRouteHandler({
router, // FileRouter (required)
config: { // S3 config (or use env vars)
bucket: "my-bucket",
region: "us-east-1",
accessKeyId: "...",
secretAccessKey: "...",
endpoint: "...", // MinIO, R2, Wasabi
forcePathStyle: true,
cdnBaseUrl: "https://cdn.example.com",
presignedUrlExpiry: 3600,
},
hooks: { ... }, // LifecycleHooks
rateLimit: { ... }, // RateLimitConfig
processing: { ... }, // ProcessingPipelineConfig
s3ClientFactory: (cfg) => createCustomClient(cfg),
});Related Packages
@uploadbox/core— Core utilities, builder pattern, S3 operations@uploadbox/react— React components and hooks
License
MIT
