Next.js Discord

Discord Forum

Can't Put Object in S3 with NextJS

Answered
Donald Louch posted this in #help-forum
Open in Discord
Avatar
Hello,

I'm trying to create a upload function with the @aws-sdk/client-s3 package and I can't seem to get files uploaded. When I try to upload files it just says PUT URL?x-id=PutObject net::ERR\_FAILED and

TypeError: Failed to fetch

  at FetchHttpHandler.handle (fetch-http-handler.js:81:13)
  
  at async flexibleChecksumsResponseMiddleware.js:20:20
  
  at async throw-200-exceptions.js:10:20
  
  at async deserializerMiddleware.js:2:26


I'm not sure what's wrong?!

My Tech Stack Is:

- NextJS (15 Canary)
- React/React-Dom (19 RC)
- @aws-sdk/client-s3 (3.670.0)
- Backblaze S3 (for my s3 client)
- @mantine/dropzone (7.13.2)
- @mantine/core (7.13.2)
- Among Others

My Code Is:
Answered by Donald Louch
After further googling I think I have fixed it?!

I needed to add "use server" at the top of my app/actions/s3File.ts file!
View full answer

4 Replies

Avatar
// app/actions/s3File.ts

import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"
import { revalidatePath } from "next/cache"

type S3Payload = {
  uploadDestination: string
  bucket: string
  hostName: string
  uploadEndpoint: string
}

export async function uploadFileToS3( formData: FormData, payload: S3Payload ): Promise<any[]> {
  const s3 = new S3Client({
    endpoint: `https://${process.env.NEXT_PUBLIC_S3_HOST_NAME}:443`,
    region: process.env.NEXT_PUBLIC_S3_REGION,
    credentials: {
      accessKeyId: process.env.NEXT_PUBLIC_S3_ACCESS_KEY_ID!,
      secretAccessKey: process.env.NEXT_PUBLIC_S3_SECRET_ACCESS_KEY!,
    },
  })
  
  const { uploadDestination, bucket, hostName, uploadEndpoint } = payload

  try {
    const files = formData.getAll("file") as File[]
    
    const response = await Promise.all(
      files.map(async (file) => {
        const {name: fileName, type: fileType, lastModified: date} = file
        const fileExtension = fileType.split("/")\[1\]
        const fileID = `${uploadDestination}\_CUSTOMRANDOMID` as string
        const filePath = `${uploadDestination}/${fileID}.${fileExtension}`
        
        const arrayBuffer = await file.arrayBuffer()
        const buffer = Buffer.from(arrayBuffer)
        
        const uploadFileObject = {
          "acl": "public-read",
          "Bucket": bucket,
          "Key": filePath,
          "Body": buffer,
          "ContentType": fileType,
        }
        
        const uploadFile = new PutObjectCommand(uploadFileObject)
        const upload = await s3.send(uploadFile)
        
        const isFileUploaded = upload.$metadata.httpStatusCode === 200 ? true : false
        
        console.log("Upload RES", upload)
      })
    )
    
    revalidatePath("/")
    return response
  } catch (error) {
    console.error("Error uploading file(s) to S3:", error)
    throw new Error("Failed to upload file(s) to S3.")
  }
}
// uploadPage/component.tsx

...

import { Dropzone, DropzoneProps, IMAGE_MIME_TYPE } from '@mantine/dropzone';
import { uploadFileToS3 } from "@/app/actions/s3File";

...

async function handleOnSubmit(e: any) {
  setUploading(true)
 
  const files = e
  const uploadDestination = mediaType
  
  try {
    const formData = new FormData()
    
    files.forEach((file) => formData.append("file", file))
    
    await uploadFileToS3(formData, {
      uploadDestination, // Picture | Video | Audio | ECT
      bucket: process.env.NEXT_PUBLIC_S3_BUCKET_NAME!,
      hostName: process.env.NEXT_PUBLIC_S3_HOST_NAME!,
      uploadEndpoint: `https://${process.env.NEXT\_PUBLIC\_S3\_BUCKET\_NAME!}.${process.env.NEXT\_PUBLIC\_S3\_HOST\_NAME!}`
    })
  } catch (error) {
    console.error("File(s) couldn't be uploaded to S3", error)
  }
}

return <>
  ...

  <Dropzone
    onDrop={(files) => handleOnSubmit(files)}
    onReject={(files) => console.log('rejected files', files)}
    loading={isUploading}
    bg="none"
    radius="md"
    c="white"
    {...props}
  >
    <Group justify="center" gap="2rem" style={{ pointerEvents: 'none' }} py="4rem">
      <Dropzone.Accept>
        <FileUploadIcon variant="twotone" />
      </Dropzone.Accept>
      
      <Dropzone.Reject> 
        <Cancel01Icon variant="twotone" />
      </Dropzone.Reject>
      
      <Dropzone.Idle>
        <CloudUploadIcon variant="twotone" size="5rem" />
      </Dropzone.Idle>
      
      <Stack gap="0" m="0" p="0">  
        <Title c="white" lh="1" fw="900" ff="text" ta={{base: "center", lg: "left"}}>
          {uploadTitle ? uploadTitle : "Upload Media"}
        </Title>
        <Text size="sm" c="grey" lh="1" ta={{base: "center", lg: "left"}}>
      {helperText ? helperText : \`Please note that once you have selected your media or media's you MUST click on the "<strong>Confirm Media Upload</strong>" Button to upload your media.\`}
        </Text>
       </Stack>
    </Group>  
  </Dropzone>
  ...
</>
Avatar
I have created an issue request on the aws-sdk-js-v3 GitHub Repo: https://github.com/aws/aws-sdk-js-v3/issues/6574
Avatar
After further googling I think I have fixed it?!

I needed to add "use server" at the top of my app/actions/s3File.ts file!
Answer