This page is a preview. Click here to exit preview mode.

Dani's Blog.

Uploading Assets using Amplify with Remix 2

This blog post was inspired by my work to incorporate uploading assets (images, music, etc.) to my Amplify backend while the app itself is written in Remix (Remix version 2).

Background: Using Amplify to upload assets in the typical SPA (single-page application) is trivial. There are several examples in the official Amplify documentation about this so I will leave that to the reader to investigate if you so please. The trouble comes in when doing the asset uploading via SSR (Server-Side Rendering). This is because of how the authorization is passed for the Amplify actions as well as how the SSR context is generated.

The first thing we want is to verify that we have amplify set up with Auth (this blog will assume you are using out-of-the-box Cognito Auth with Amplify) and Storage (this post assumes a standard S3 bucket set up with CRUD permissions for authenticated users)

The first issue I ran into was uploading the file in remix. There are several examples that I had found that potentially would work. For this post we will be using the `useFetcher` hook provided by remix. For me, this works the best because of my use of a custom photo cropper which uses `react-image-crop`. This component will potentially be outsourced in another post.

Essentially what want is an event handler which will take a file object, insert it into a formData object and submit that via the fetcher.

First we create the fetcher with a unique key for this action:

const fetcher = useFetcher({key: 'upload-asset-fetcher'});

Next we can add our handler which assumes it will be passed in a file Blob of the asset to be uploaded.

const handleUpload = async (file: File) => {

const formData = new FormData();


formData.append('file', file);

fetcher.submit(formData, { method: 'POST', encType: 'multipart/form-data'});


Now in Remix we need to define our action function to be able to accept this multipart form. This is where the code gets a bit hacky. I will update the blog post if a better/easier approach is proposed. For now, our action looks like this:

export const action:ActionFunction=async ({ request }) => {

const formData=await unstable_parseMultipartFormData(request,async ({name,contentType,data,filename, }):Promise<string|File|null|undefined> => {const dataArray1= [];
for await (const x of data) {dataArray1.push(x); }
if (!name||name!=="file") {

const decoder=new TextDecoder();

const uiarr=dataArray1[0] as Uint8Array;

return decoder.decode(uiarr.buffer);


const blob=new Blob(dataArray1, { type:"image/jpeg" });

return blob as File;

}, );
const file=formData.get("file");

const key=formData.get("key");

if (!key||!file) {

return {error:"No key or file provided", };


More Stories

Cover Image for Welcome to My Blog

Welcome to My Blog

Welcome post. I'll write stuff here.

Danielle "Dani" Brear
Danielle "Dani" Brear