Better Upload Documentation
repository·main·Indexed 23 days ago
https://github.com/nic13gamer/better-uploadA React-focused library for simplifying direct-to-S3 file uploads to any S3-compatible service. It provides client-side tools like the useUploadFiles hook and UploadDropzone component, and a server-side Router for managing upload routes, constraints, and authentication. Compatible with Next.js, TanStack Start, Remix, Express, Fastify, Hono, and Elysia, with built-in clients for AWS, Cloudflare R2, Tigris, Backblaze B2, DigitalOcean Spaces, Wasabi, MinIO, and Linode.
What's inside Better Upload
- Better Upload provides simple and easy file upload capabilities for React applications. It allows users to upload files directly to any S3-compatible storage service with minimal configuration. It is designed to work with any React-based framework, including Next.js and Tanstack Start.
Introduction to Better Upload
mainBetter Upload is a library designed for simple and easy file uploads in React applications. It allows you to upload files directly to any S3-compatible service with minimal setup, ensuring you maintain full ownership and control over your data and S3 buckets.Understand the documentation project structure
mainThe documentation site is built using Next.js and Fumadocs. Key files and routes include:
lib/source.ts: Contains the code for the content source adapter. Theloader()function provides the interface to access your content.app/layout.config.tsx: Contains shared options for layouts.app/(home): Route group for the landing page and other top-level pages.app/docs: The main documentation layout and pages.app/api/search/route.ts: The Route Handler responsible for search functionality.
Manage multipart uploads
mainFor large files, use the multipart upload helpers to break the upload into parts. The workflow is:
createMultipartUpload: Initialize the upload and get anuploadId.uploadPart: Upload individual chunks of data.completeMultipartUpload: Finalize the upload by providing an array ofpartNumberandeTagpairs.abortMultipartUpload: Cancel the upload if necessary.
import { createMultipartUpload, uploadPart, completeMultipartUpload, abortMultipartUpload } from '@better-upload/server/helpers'; // 1. Create const { uploadId } = await createMultipartUpload(s3, { bucket: 'my-bucket', key: 'large-file.zip', contentType: 'application/zip', }); // 2. Upload part const { eTag } = await uploadPart(s3, { bucket: 'my-bucket', key: 'large-file.zip', uploadId: '...', partNumber: 1, body: partData, }); // 3. Complete await completeMultipartUpload(s3, { bucket: 'my-bucket', key: 'large-file.zip', uploadId: '...', parts: [ { partNumber: 1, eTag: '...' }, { partNumber: 2, eTag: '...' }, ], }); // Or 4. Abort await abortMultipartUpload(s3, { bucket: 'my-bucket', key: 'large-file.zip', uploadId: '...', });Handle upload lifecycle events with useUploadFile
mainYou can intercept various stages of the upload process by providing callback functions in the
useUploadFileoptions object:onBeforeUpload: Called before requesting the pre-signed URL. Use this to modify the file (e.g., resizing, renaming) by returning a newFileobject, or throw an error to reject the upload.onUploadBegin: Called after the server responds with the pre-signed URL, but before the file starts uploading to S3.onUploadProgress: Called whenever the upload progress changes. Provides{ file }wherefile.progressis a value between 0 and 1.onUploadComplete: Called after a successful upload.onError: Called if the upload fails or if inputs are invalid. Provides theerrorobject.onUploadSettle: Called after the upload finishes, regardless of whether it succeeded or failed.
useUploadFile({ route: 'profile', onBeforeUpload: ({ file }) => { // rename the file return new File([file], 'renamed-' + file.name, { type: file.type }); }, onUploadBegin: ({ file, metadata }) => { console.log('Upload begin'); }, onUploadProgress: ({ file }) => { console.log(`Upload progress: ${file.progress * 100}%`); }, onUploadComplete: ({ file, metadata }) => { console.log('File uploaded'); }, onError: (error) => { console.log(error.message); }, onUploadSettle: ({ file, metadata }) => { console.log('Upload settled'); }, });Configure the `@better-upload/server` Router
mainThe
Routerobject is the core configuration for your upload server. It defines the storage client, the target bucket, and a set of upload routes. Each route can have specific constraints like allowed file types, whether multiple files are permitted, and a maximum number of files.Key properties of the
Routerobject:client: An S3-compatible client (e.g.,aws(),cloudflare(),backblaze(),tigris()).bucketName: The name of the destination bucket.routes: An object where keys are route names and values are configurations created viaroute().
import { route, type Router } from '@better-upload/server'; import { aws } from '@better-upload/server/clients'; const router: Router = { client: aws(), bucketName: 'my-bucket', routes: { images: route({ fileTypes: ['image/*'], multipleFiles: true, maxFiles: 4, }), }, };Compatibility and Framework Support
mainQuickstart: Single file uploads with Better Upload
mainThis guide demonstrates how to set up single file uploads in a React application using Better Upload. The process involves installing the server and client packages, configuring a server route to generate pre-signed S3 URLs, and using the
@better-upload/clienthooks with a pre-built UI component.Prerequisites:
- An S3-compatible bucket (AWS S3, Cloudflare R2, etc.).
- A React framework (e.g., Next.js).
Steps:
Install dependencies:
npm i @better-upload/server @better-upload/clientSet up the server: Configure a server route (e.g.,
profile) to generate pre-signed URLs for your S3 bucket.Install UI components: Use the shadcn CLI to add the pre-built upload button:
npx shadcn@latest add @better-upload/upload-buttonImplement the Uploader component: Use the
useUploadFilehook to connect your UI to the server route.Configure CORS: Ensure your S3 bucket has the correct CORS configuration to allow requests from your application's domain.
'use client'; // only for Next.js import { useUploadFile } from '@better-upload/client'; import { UploadButton } from '@/components/ui/upload-button'; export function Uploader() { const { control } = useUploadFile({ route: 'profile', }); return <UploadButton control={control} accept="image/*" />; }Setup an upload route for TanStack Form
mainConfigure your server-side upload route using
@better-upload/server. In this example, we use the Next.js adapter and AWS client. Therouteconfiguration defines the constraints for the upload, such asmultipleFiles,maxFiles, andmaxFileSize. You can also useonBeforeUploadto customize the object key (e.g., adding a prefix likeform/).import { route, type Router } from '@better-upload/server'; import { toRouteHandler } from '@better-upload/server/adapters/next'; import { aws } from '@better-upload/server/clients'; const router: Router = { client: aws(), bucketName: 'my-bucket', routes: { form: route({ multipleFiles: true, maxFiles: 5, maxFileSize: 1024 * 1024 * 5, // 5MB onBeforeUpload() { return { generateObjectInfo: ({ file }) => ({ key: `form/${file.name}` }), }; }, }), }, }; export const { POST } = toRouteHandler(router);Integrate `@better-upload/server` with Next.js
mainTo use Better Upload in a Next.js App Router environment, use the
toRouteHandleradapter from@better-upload/server/adapters/next. This converts yourRouterconfiguration into a standard Next.js Route Handler.import { route, type Router } from '@better-upload/server'; import { toRouteHandler } from '@better-upload/server/adapters/next'; import { aws } from '@better-upload/server/clients'; const router: Router = { client: aws(), bucketName: 'my-bucket', routes: { images: route({ fileTypes: ['image/*'], multipleFiles: true, maxFiles: 4, }), }, }; export const { POST } = toRouteHandler(router);Use TanStack Query with Better Upload
mainIf you prefer not to use the built-in Better Upload hooks, you can integrate the library with TanStack Query by using the
uploadFile(for single files) oruploadFiles(for multiple files) functions inside auseMutationhook. This allows you to leverage TanStack Query's mutation lifecycle (onSuccess,onError,isPending) for your upload logic.import { uploadFiles } from '@better-upload/client'; import { useMutation } from '@tanstack/react-query'; // Inside your component const { mutate: upload, isPending } = useMutation({ mutationFn: async (files: File[]) => { return uploadFiles({ files, route: 'form', onFileStateChange: ({ file }) => { // handle progress }, }); }, onSuccess: ({ files, failedFiles, metadata }) => { // handle success }, onError: (error) => { // handle error }, });Install the UploadProgress component
mainYou can install the
UploadProgresscomponent using the shadcn CLI or manually.Via CLI
Run the following command to add the component directly:
npx shadcn@latest add @better-upload/upload-progressManual Installation
- Install the required dependencies:
npm i lucide-react react-dropzone- Ensure you have shadcn/ui configured in your project with the
progresscomponent installed. - Copy the component source code into your project and update the import paths to match your local directory structure.