Can you use UploadThing with Astro?

Think “S3 for the rest of us”. Just npm install and you’re good to go.

This is UploadThing, presenting themselves as a plug-n-play file upload solution. Let’s put that to the test. Can we use UploadThing with Astro?

Disclaimer: This is not a tutorial. UploadThing doesn’t actually support Astro yet. I’m just messing about for fun. I don’t recommend doing this for a production Astro site yet, although I’m sure support for Vite frameworks will get there eventually.

Getting started

We need an Astro project. So…

Terminal window
npm create astro@latest -- . --template minimal -y

Then we can grab UploadThing’s package from NPM. I’m also using the React components. I tried my best to get their Solid components to work, but Solid caused all sorts of weird errors. Maybe one day.

Terminal window
npx astro add react -y
npm i uploadthing @uploadthing/react react-dropzone

Setting up UploadThing

After signing up for the service, I grabbed some example code from their docs to define the file router. It reminds me a little of tRPC, which was cool. I also had to do some weird environment variable stuff to get it to work. They seem to rely on process.env for the environement variables? It’s probably a difference between Vite and Next.js causing the issue, but we’re able to assign them at runtime to get around it.

src/lib/uploadthing.ts
import { createUploadthing } from 'uploadthing/server'
import type { FileRouter } from 'uploadthing/server'
// HackyThing™️
process.env.UPLOADTHING_SECRET = import.meta.env.UPLOADTHING_SECRET
process.env.UPLOADTHING_ID = import.meta.env.UPLOADTHING_ID
const f = createUploadthing()
export const uploadRouter = {
videoAndImage: f({
image: {
maxFileSize: '4MB',
maxFileCount: 4,
},
video: {
maxFileSize: '16MB',
},
}).onUploadComplete(data => {
console.log('upload completed', data)
}),
} satisfies FileRouter
export type OurFileRouter = typeof uploadRouter

Next comes the API. Apart from remapping variable names it was pretty much a copy-paste job. UploadThing handle two methods (GET and POST), which handle requests based on our defined router.

import type { APIContext } from "astro";
import { uploadRouter } from "../../lib/uploadthing"
import { createNextRouteHandler } from 'uploadthing/next'
const { GET, POST } = createNextRouteHandler({
router: uploadRouter,
})
export const get = (context: APIContext) => GET();
export const post = (context: APIContext) => POST(context.request);

Making the UI

I’m using the provided React components, which require some TSX specific syntax for the generic type, so I’m wrapping the function in my own component, before exporting that to be reused elsewhere.

src/components/OurUploadButton.tsx
import { UploadButton } from "@uploadthing/react";
import "@uploadthing/react/styles.css"
import type { OurFileRouter } from "../lib/uploadthing";
export const OurUploadButton = () => (
<UploadButton<OurFileRouter>
endpoint="videoAndImage"
onClientUploadComplete={res => {
// Do something with the response
console.log("Files: ", res);
alert("Upload Completed");
}}
onUploadError={(error: Error) => {
// Do something with the error.
alert(`ERROR! ${error.message}`);
}}
/>
);

Then we can plug this into our page.

src/pages/index.astro
---
import { OurUploadButton } from '../components/OurUploadButton'
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>AstroThing</title>
</head>
<body>
<h1>AstroThing</h1>
<OurUploadButton client:only />
</body>
</html>

Let’s Upload a Thing!

With all that done, we can run the dev server and test it out!

Uploading works fine both in dev and production, but in dev instead of the success message, we get an error about process (which doesn’t seem to be from my previous hacks) but the uploads still work fine.

Another side-effect seems to be that the CSS imported in our React component contains a CSS reset, breaking the default styles. I’ve not tried UploadThing elsewhere, so I don’t know if this is a common problem. My guess looking at the class names is a Tailwind reset might be involved, so if you use Tailwind you’ll probably be fine.

Reflections

UploadThing is a cool service. If all you need is a quick file upload, it’s perfect! Well, almost. If you’re using Astro I’d hold off for now. UploadThing is still pretty new, so I’m sure their agnostic support will improve over time. Once the issues with Vite (and whatever happened to the CSS) are addressed, it’ll be a great option for just adding file uploads to forms, profiles, documents, whatever your use-case is.