Displaying Images on Your Website via Amazon S3 and Cloudfront

Displaying Images on Your Website via Amazon S3 and Cloudfront

Amazon S3 is a very affordable place to store your images, and Cloudfront allows you to serve those images lightning fast. Setting it all up is pretty easy.

Creating an S3 Bucket

Log into your AWS account, browse to S3, and click the Create Bucket button.

On the "Create Bucket" page, do the following:

  1. Give your bucket a unique name. (This name must be unique within all of AWS - you'll get an an error if you submit a name used by someone else).
  2. Leave "ACLs disabled" checked under Object Ownership.
  3. Uncheck the "Block all public access" option. Your images must be publicly accessible to be viewable by you and other users in web browsers.
  4. Check the acknowledgement in the yellow box in this section.
  5. Leave "Bucket Versioning" disabled.
  6. No need to add any tags.
  7. Leave "Default Encryption" as it is.
  8. Click the Create Bucket button.

To summarize, all you have to do is add a unique name, uncheck the "Block all public access" option, and check the acknowledgment. Piece of cake.

Here's what it should look like if you're curious:

S3 Bucket Settings Top

S3 Bucket Settings Acknowledgement

S3 Bucket Settings Bottom

After you create the bucket, it will send you back to your list of buckets, and you should see the new bucket you created. Click on that.

Add a Bucket Policy

Making the bucket public makes the bucket itself public, but it doesn't make the assests in the bucket public. I'm sure there's a good reason for this.

In order to make the assets themselves public, you need a bucket policy. I just Googled this and came up with this Stack Overflow post, which provides a helpful explanation of what each attribute of the policy means:

AWS S3 Policy to show images (read-only). Please verify

We're going to create a policy that looks like this:

{
    "Version": "2012-10-17",
    "Id": "Policy1731985303005",
    "Statement": [
        {
            "Sid": "Stmt1731985277003",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::bucketname/*"
        }
    ]
}

You can generate your own by going to the AWS Policy Generator.

To do this, choose "S3 Bucket Policy" in Step 1 and add a Statement with these settings:

  1. Leave Effect set to "Allow."
  2. Enter "*" in the Principal field.
  3. In the Actions dropdown, choose the "GetObject" action (that's the only one you need).
  4. Copy and paste your ARN, which you can find on the Properties tab of you S3 Bucket and add IMPORTANTLY add this "/*" to the end of it.

To reiterate, you have to add a "/*" to the end of your ARN or it won't work. This allows the policy to pertain to all assets and subdirectories in the bucket.

In the example above, this looks like this:

arn:aws:s3:::bucketname/*

Then click Add Statement and then click Generate Policy. Copy and paste the output into the Bucket Policy field back in S3.

Some screenshots of what it should look like:

Policy Generator 1st 2 Steps

Policy Generator Choose Action

Policy Generator Action Added

Policy Generator Policy

Okay, took a while to explain that, but there's really not much to it.

Here's a great YouTube video that explains in more detail if you're curious:

Setting up S3 for Website Images

Test It Out

To test it out, all you have to do is upload a file, click on the object, and then click the "Object URL" link, which looks like this:

Object Overview

If you missed a step, you'll get an error message that looks like this in your browser when you click the link (if you want to verify, you can remove the policy and then add it back - it works instantly when the policy is added back).

<Error>
  <Code>AccessDenied</Code>
  <Message>Access Denied</Message>
  <RequestId>RC8FNKNJ2093XTCK</RequestId>
  <HostId>SbAA0sXkj1AJ5dEEYvV9mNgoIpbUCzo/2sFUyrtdtWoGywN5eWzt2RD/1btIfVzrsjcFZ4KWS868qhubrmzhmX16iaELx+O4eiKbhUWjxPk=</HostId>
</Error>

Set up a Cloudfront Distribution

Here's a great post on setting up a Cloudfront distribution to add a CDN in front of your bucket:

Easy CDN using S3 and CloudFront

To summarize, head over to Cloudfront, click Create distribution, and do the following:

  1. In the "Origin domain" field, choose the S3 bucket you just created.
  2. Under "Origin Access", choose the "Origin access control settings" option.
  3. Create a new OAC, leaving all the settings as is (it's helpful to add a description).
  4. When you do this you'll see a "You must update the S3 bucket policy" warning, which we'll soon address.
  5. The only other thing you'll need to do is choose a security setting in the "Web Application Firewall (WAF)" section. In this case, I chose "Do not enable security protections," but you can do whatever's right for you.
  6. Leave all other settings as is and click the Create Distribution button.

When you do this, they'll send you to the created distribution with a warning that looks like this:

Update Policy

Click the "Copy policy" button and then click the link to access your S3 Bucket Policy. Replace the bucket policy we added previously with the new one provided by Cloudfront.

Once the Cloudfront Distribution finishes deploying you can test out the image you uploaded previously. The S3 URL will no longer work:

https://jcharrington.s3.us-east-2.amazonaws.com/old-computer-night.webp

However, it will work when you replace the domain of your S3 bucket:

jcharrington.s3.us-east-2.amazonaws.com

with the domain of your Cloudfront distribution:

d2xeafg0m9t45n.cloudfront.net

So this URL now works:

https://d2xeafg0m9t45n.cloudfront.net/old-computer-night.webp

So, what we've done is make the S3 bucket accessible only to Cloudfront and the images served via Cloudfront are now publicly accessible. Pretty cool.

Make the images accessible to your Next.js app

If you're using Next.js for your site, you'll need to add the Cloudfront domain to your Next.js config (next.config.mjs) so the next/image <Image /> component can use these images. If you don't do this, you'll get this error:

Error: Invalid src prop (https://d2xeafg0m9t45n.cloudfront.net/old-computer-night.webp) on `next/image`, hostname "d2xeafg0m9t45n.cloudfront.net" is not configured under images in your `next.config.js`
See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host

Next.js Invalid src prop error

To fix this, update your Next.js config to look like this:

const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'd2xeafg0m9t45n.cloudfront.net'
      }
    ]
  }
}

Restart your Next app, and you should be good to go.

Sources

  1. Setting up S3 for Website Images [YouTube]
  2. [AWS S3 Policy to show images (read-only). Please verify [Stack Overflow]]
  3. Easy CDN using S3 and CloudFront [DEV]