āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/nic13gamer/better-upload/quickstart-single ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
You can have file uploads in your React app in a few minutes with Better Upload. This guide will walk you through the steps to set it up with any React framework.
Before you start, make sure you have an S3-compatible bucket ready. You can use AWS S3, Cloudflare R2, or any other S3-compatible service.
<PageSelect pages={[ { href: '/docs/quickstart', title: 'Multiple files', description: 'Upload more than one file at once.', }, { href: '/docs/quickstart-single', title: 'Single files', description: 'Upload only a single file at once.', }, ]} />
Install the better-upload package, as well as the AWS S3 Client.
npm i better-upload @aws-sdk/client-s3
</Step>
<Step>
Your server will create pre-signed URLs, which the client uses to upload files directly to the S3 bucket.
Change my-bucket to your bucket name, and configure the S3 client as needed.
import { S3Client } from '@aws-sdk/client-s3';
import {
createUploadRouteHandler,
route,
type Router,
} from 'better-upload/server';
const s3 = new S3Client(); // [!code highlight]
const router: Router = {
client: s3,
bucketName: 'my-bucket', // [!code highlight]
routes: {
demo: route({
fileTypes: ['image/*'],
}),
},
};
export const { POST } = createUploadRouteHandler(router);
import { createFileRoute } from '@tanstack/react-router';
import { handleRequest, route, type Router } from 'better-upload/server';
const s3 = new S3Client(); // [!code highlight]
const router: Router = {
client: s3,
bucketName: 'my-bucket', // [!code highlight]
routes: {
demo: route({
fileTypes: ['image/*'],
}),
},
};
export const Route = createFileRoute('/api/upload')({
server: {
handlers: {
POST: async ({ request }) => {
return handleRequest(request, router);
},
},
},
});
import { ActionFunctionArgs } from '@remix-run/node';
import { handleRequest, route, type Router } from 'better-upload/server';
const s3 = new S3Client(); // [!code highlight]
const router: Router = {
client: s3,
bucketName: 'my-bucket', // [!code highlight]
routes: {
demo: route({
fileTypes: ['image/*'],
}),
},
};
export async function action({ request }: ActionFunctionArgs) {
return handleRequest(request, router);
}
// When using a separate backend server, make sure to update the `api` option on the client hooks.
import { Hono } from 'hono';
import { handleRequest } from 'better-upload/server';
const app = new Hono();
const s3 = new S3Client(); // [!code highlight]
const router: Router = {
client: s3,
bucketName: 'my-bucket', // [!code highlight]
routes: {
demo: route({
fileTypes: ['image/*'],
}),
},
};
app.post('/upload', (c) => {
return handleRequest(c.req.raw, router);
});
In the example above, we create the upload route demo. Learn more about upload routes here.
You can run code before uploads in the server. Use the onBeforeUpload callback:
import { RejectUpload, route, type Router } from 'better-upload/server';
const auth = (req: Request) => ({ id: 'fake-user-id' }); // [!code highlight]
const router: Router = {
client: s3,
bucketName: 'my-bucket',
routes: {
demo: route({
fileTypes: ['image/*'],
// [!code ++:7]
onBeforeUpload: async ({ req, file, clientMetadata }) => {
const user = await auth(req);
if (!user) {
throw new RejectUpload('Not logged in!');
}
},
}),
},
};
</Accordion>
<Accordion title="Changing S3 object key">
You can change the S3 object key to any value you want. Use the onBeforeUpload callback:
const router: Router = {
client: s3,
bucketName: 'my-bucket',
routes: {
demo: route({
fileTypes: ['image/*'],
// [!code ++:7]
onBeforeUpload: async ({ req, file, clientMetadata }) => {
return {
objectInfo: {
key: `files/${file.name}`,
},
};
},
}),
},
};
</Accordion>
</Accordions>
</Step>
<Step>
<Uploader /> componentWe will now build our UI using pre-built components. We'll use <UploadButton /> for single file uploads.
Install it via the shadcn CLI:
npx shadcn@latest add @better-upload/upload-button
We'll also use the useUploadFile hook. The complete code looks like this:
'use client'; // For Next.js
import { useUploadFile } from 'better-upload/client';
import { UploadButton } from '@/components/ui/upload-button';
export function Uploader() {
const { control } = useUploadFile({
route: 'demo',
});
return <UploadButton control={control} accept="image/*" />;
}
Learn more about the hooks here.
</Step> <Step>Now place the <Uploader /> component in your app.
import { Uploader } from '@/components/uploader';
export default function Page() {
return (
<main className="flex min-h-screen flex-col items-center justify-center">
<Uploader />
</main>
);
}
</Step>
<Step>
You can now run your app and upload images directly to any S3-compatible service!
If you plan on uploading files larger than 5GB, take a look at multipart uploads.
<Accordions> <Accordion title="CORS Configuration">Make sure to also correctly configure CORS on your bucket. Here is an example:
[
{
"AllowedOrigins": [
"http://localhost:3000",
"https://example.com" // Add your domain here
],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"]
}
]
Learn more about CORS here.
</Accordion> </Accordions> </Step> </Steps>ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā