Next.js Discord

Discord Forum

Image Uploads to S3 Require Page Refresh to Display - Caching Issues?

Answered
Yellow croaker posted this in #help-forum
Open in Discord
Yellow croakerOP
I'm encountering a strange issue where images uploaded to my S3 bucket aren't immediately displayed in the UI. Here's what happens:
- I use import Image from 'next/image';
- User uploads an image to S3 (upload confirmed successful).
- Image doesn't display in UI until a full page refresh. - Deleting and re-uploading the same image works (if a page refresh occurred in between).
- Console shows a GET request error for the image URL. 403 forbidden

Here is the action which is used to upload the image:

export async function getSignedURL(
  type: string,
  size: number,
  checksum: string,
  resumeId: string,
) {
  const session = await getCurrentUser();

  if (!session) {
    return { failure: 'not authencticated' };
  }

  if (!acceptedTypes.includes(type)) {
    return { failure: 'invalid type' };
  }
  if (size > maxFileSize) {
    return { failure: 'file too large' };
  }
  const putObjectCOmmand = new PutObjectCommand({
    Bucket: process.env.AWS_BUCKET_NAME,
    Key: generateFileName(),
    ContentType: type,
    ContentLength: size,
    ChecksumSHA256: checksum,
    Metadata: {
      userId: session.id,
    },
  });

  const signedURL = await getSignedUrl(s3, putObjectCOmmand, {
    expiresIn: 60,
  });

  const imageResult = db.personalInfo.update({
    where: { resumeId: resumeId },
    data: {
      imagePath: signedURL.split('?')[0],
    },
  });
  revalidatePath(`/resume/${resumeId}`);
  return { success: { url: signedURL, imageId: imageResult } };
}


Here is part of the code where i use the image:

 <div className="uploaded-image-container">
  <Image
    src={resume.personalInfo?.imagePath}
    width={100}
    height={100}
    alt="ALT"/>
  <Button
  variant="light"
  onClick={() =>
  deleteImage(resume.id, resume.personalInfo.imagePath)
  }>Delete picture
</Button>
</div>
Answered by Yellow croaker
Hey i got it sorted out! I had stupid mistake on My handlesave function, which was i was supposed to share here earlier but forgot...

Basically i saved The signedUrl to The user, and after that i revalidatedPath, do getting The Image was not possible because it were not saved yet. After The revalidate i sent The Image to s3. Had wrong order on My s3!

Thanks anyway taking your Time and helping me out!
View full answer

16 Replies

Yellow croakerOP
Here is the deleteImage action if its necessary

export async function deleteImage(resumeId: string, imagePathName?: string) {
  const session = await getCurrentUser();
  if (!session) {
    return { failure: 'not authencticated' };
  }
  const imageItem = await db.personalInfo.update({
    where: {
      resumeId: resumeId,
    },
    data: {
      imagePath: null,
    },
  });
  console.log('imageEIte', imageItem);
  const deleteObjectCommand = new DeleteObjectCommand({
    Bucket: process.env.AWS_BUCKET_NAME,
    Key: imagePathName?.split('/').pop(),
  });
  console.log('deleteObjectCommand', deleteObjectCommand);
  await s3.send(deleteObjectCommand);
  revalidatePath(`/resume/${resumeId}`);
}
@Yellow croaker I'm encountering a strange issue where images uploaded to my S3 bucket aren't immediately displayed in the UI. Here's what happens: - I use *import Image from 'next/image';* - User uploads an image to S3 (upload confirmed successful). - Image doesn't display in UI until a full page refresh. - Deleting and re-uploading the same image works (if a page refresh occurred in between). - Console shows a GET request error for the image URL. 403 forbidden Here is the action which is used to upload the image: js export async function getSignedURL( type: string, size: number, checksum: string, resumeId: string, ) { const session = await getCurrentUser(); if (!session) { return { failure: 'not authencticated' }; } if (!acceptedTypes.includes(type)) { return { failure: 'invalid type' }; } if (size > maxFileSize) { return { failure: 'file too large' }; } const putObjectCOmmand = new PutObjectCommand({ Bucket: process.env.AWS_BUCKET_NAME, Key: generateFileName(), ContentType: type, ContentLength: size, ChecksumSHA256: checksum, Metadata: { userId: session.id, }, }); const signedURL = await getSignedUrl(s3, putObjectCOmmand, { expiresIn: 60, }); const imageResult = db.personalInfo.update({ where: { resumeId: resumeId }, data: { imagePath: signedURL.split('?')[0], }, }); revalidatePath(`/resume/${resumeId}`); return { success: { url: signedURL, imageId: imageResult } }; } Here is part of the code where i use the image: js <div className="uploaded-image-container"> <Image src={resume.personalInfo?.imagePath} width={100} height={100} alt="ALT"/> <Button variant="light" onClick={() => deleteImage(resume.id, resume.personalInfo.imagePath) }>Delete picture </Button> </div>
try Using this:
const putObjectCOmmand = new PutObjectCommand({
  Bucket: process.env.AWS_BUCKET_NAME,
  Key: generateFileName(),
  ContentType: type,
  ContentLength: size,
  ChecksumSHA256: checksum,
  ACL: 'public-read', // Add this line
  Metadata: {
    userId: session.id,
  },
});
use signed url for getting the image
this should work. I'm using the same thing in one of my project and its working for me
@Anay-208 use signed url for getting the image
Yellow croakerOP
How i should start working on that, because i thought that having the url saved to the user and then calling it, would be sufficient?

So instead of just calling the imagePath like this:

                  <Image
                    src={resume.personalInfo?.imagePath}
                    width={100}
                    unoptimized={true}
                    height={100}
                    alt="ALT"
                    //className="uploaded-image"
                  />

I should create an function which i give to the src, and that function returns a URL to the image?
@Anay-208 You can try
Yellow croakerOP
Yeah so basically i need to make something like the getSignedUrl to get the access to the pictures.

Like this which is used to upload the images to the S3 and save the URL address to personalInformation.

It's just strange, because i have access to the images from my browser Via the link that the personalInfo has, but my application does not, but is that because the lack of signedUrl to fetch the images?
export async function getSignedURL(
  type: string,
  size: number,
  checksum: string,
  resumeId: string,
) {
  const session = await getCurrentUser();
  console.log('Session in resumeACtion', session);
  if (!session) {
    return { failure: 'not authencticated' };
  }

  if (!acceptedTypes.includes(type)) {
    return { failure: 'invalid type' };
  }
  if (size > maxFileSize) {
    return { failure: 'file too large' };
  }

  const putObjectCOmmand = new PutObjectCommand({
    Bucket: process.env.AWS_BUCKET_NAME,
    Key: generateFileName(),
    ContentType: type,
    ContentLength: size,
    ChecksumSHA256: checksum,
    ACL: 'public-read',
    Metadata: {
      userId: session.id,
    },
  });

  const signedURL = await getSignedUrl(s3, putObjectCOmmand, {
    expiresIn: 60,
  });

  const imageResult = db.personalInfo.update({
    where: { resumeId: resumeId },
    data: {
      imagePath: signedURL.split('?')[0],
    },
  });
  revalidatePath(`/resume/${resumeId}`);
  return { success: { url: signedURL, imageId: imageResult } };
  // revalidatePath(`/resume/${resumeId}`);
}
@Yellow croaker Yeah so basically i need to make something like the getSignedUrl to get the access to the pictures. Like this which is used to upload the images to the S3 and save the URL address to personalInformation. It's just strange, because i have access to the images from my browser Via the link that the personalInfo has, but my application does not, but is that because the lack of signedUrl to fetch the images? js export async function getSignedURL( type: string, size: number, checksum: string, resumeId: string, ) { const session = await getCurrentUser(); console.log('Session in resumeACtion', session); if (!session) { return { failure: 'not authencticated' }; } if (!acceptedTypes.includes(type)) { return { failure: 'invalid type' }; } if (size > maxFileSize) { return { failure: 'file too large' }; } const putObjectCOmmand = new PutObjectCommand({ Bucket: process.env.AWS_BUCKET_NAME, Key: generateFileName(), ContentType: type, ContentLength: size, ChecksumSHA256: checksum, ACL: 'public-read', Metadata: { userId: session.id, }, }); const signedURL = await getSignedUrl(s3, putObjectCOmmand, { expiresIn: 60, }); const imageResult = db.personalInfo.update({ where: { resumeId: resumeId }, data: { imagePath: signedURL.split('?')[0], }, }); revalidatePath(`/resume/${resumeId}`); return { success: { url: signedURL, imageId: imageResult } }; // revalidatePath(`/resume/${resumeId}`); }
This is because if I’m right, s3 doesn’t make a object public immediately
I’m not 100% sure
@Anay-208 This is because if I’m right, s3 doesn’t make a object public immediately
Yellow croakerOP
Okey so as immediadately u mean like 0.1 second, which takes to load the image, and thats why its forbidden to fetch it.

But then why would the first image show normally?
But maybe is best if i try the signed URL first and then lets see what happens!
@Anay-208 This is because if I’m right, s3 doesn’t make a object public immediately
Yellow croakerOP
Allright so i tried signedUrl, but it didnt seem to help.

In my front
  const [imageUrl, setImageUrl] = useState(resume.personalInfo?.imagePath);
...

  useEffect(() => {
    const loadImage = async () => {
      if (resume.personalInfo?.imagePath) {
        const signedUrl = await fetchImageSignedURL(
          resume.personalInfo.imagePath,
        );
        setImageUrl(signedUrl);
      }
    };

    loadImage();
  }, [resume.personalInfo?.imagePath, fetchImageSignedURL]);


...

(

       <Image
          src={imageUrl}
          width={100}
          //unoptimized={true}
          height={100}
          alt="ALT"
          //className="uploaded-image"
        />)


const s3 = new S3Client({
  region: process.env.AWS_BUCKET_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? '',
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? '',
  },
});

export const fetchImageSignedURL = async (imagePath: string) => {
  const session = await getCurrentUser();
  if (!session) {
    return { failure: 'not authencticated' };
  }

  const command = new GetObjectCommand({
    Bucket: 'bukcetName',
    Key: imagePath,
  });

  const signedUrl = await getSignedUrl(s3, command, { expiresIn: 60 });
  return signedUrl;
};

In server side i get this
error: upstream image response failed for 
https://bucketName.s3.eu-north-1.amazonaws.com/https%3A//bucketName.s3.eu-north-1.amazonaws.com/someCode?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=somecredentials_request&X-Amz-Date=somedate&X-Amz-Expires=60&X-Amz-Signature=somesignature&X-Amz-SignedHeaders=host&x-id=GetObject 403
Yellow croakerOP
This is quite strange problem. Maybe there is some sort of async problem? The Image is tried to be showed before it's loaded to aws s3 and thats why it does not show, unless i refresh the page?
if possible that you can give a reproduction report, it would be good
@Anay-208 if possible that you can give a reproduction report, it would be good
Yellow croakerOP
Hey i got it sorted out! I had stupid mistake on My handlesave function, which was i was supposed to share here earlier but forgot...

Basically i saved The signedUrl to The user, and after that i revalidatedPath, do getting The Image was not possible because it were not saved yet. After The revalidate i sent The Image to s3. Had wrong order on My s3!

Thanks anyway taking your Time and helping me out!
Answer