DEV Community

Christian Nwamba
Christian Nwamba

Posted on

Image Optimization with Next.js, AWS Amplify, and Amazon S3

Image Optimization with Nextjs 13 and Amplify Storage

Nextjs is arguably the hottest JavaScript framework today, with many awesome features that make it desirable for front-end engineers. Of all its amazing features, one that stands out is the Image component and image optimization. That’s not surprising, considering that front-end developers have always struggled with correctly implementing images on the web. The Nextjs Image component has some cool features that make it easier for developers to manage and optimize images in applications.

This article will teach us how to use some image optimization features. We will also get practical and demonstrate a scenario where we use AWS Amplify Storage (backed by AWS S3) to store and retrieve the image we want to optimize; instead of using local (static) images. We will also learn how to deploy a Nextjs 13 app to Amplify hosting.

What You Will Learn

Before we get to writing code, let’s highlight some of the things you will learn from this article:

  • Fill dimensions of the parent element: When accessing images from a remote source without knowing the image width and height, you should use the fill property. fill is a boolean property that allows the image to “fill” the parent element without explicitly setting width and height. However, you must set a position property. By default, it’s set to position: absolute.

  • Blur-up placeholder: It’s generally good UX to have a placeholder shimmer, or blur image when loading images. Nextjs Image component has a placeholder property that accepts a blur value. When accessing a dynamic image, you have to set the blurDataURL property.

  • Responsive Image: Making images responsive to all screen sizes can be a legitimate cause of headaches. It’s recommended that you use srcset and have different image sizes to make it responsive to all screens. Nextjs Image automatically generates image source sets. You have to use the sizes property to define the viewport width. For instance:

    sizes="(max-width: 768px) 100vw,(max-width: 1200px) 50vw, 33vw"

  • Text on background image: Placing text on a background image can be tricky most times but with the combo of placeholder, fill , and some CSS styling, we can implement it with the Image component.

Amplify already supports the latest version of Nextjs (13), which means you can confidently deploy all the above-listed features.

Pre-requisites

You need to have the following to follow through:

  • Nodejs ≥v14 installed (with npm or yarn)
  • Knowledge of JavaScript and React
  • A code editor (preferably VS code)
  • AWS Amplify CLI installed yarn add @aws-amplify/cli --global
  • AWS Amplify configured amplify configure

To code along, check out the Github repo here.

Create a Next.js Project

Let’s get started by bootstrapping a nextjs project. Run this command on your terminal:

yarn create next-app
Enter fullscreen mode Exit fullscreen mode

When this is done, open the project in your code editor.

cd project-name
code . (for vs code users)
Enter fullscreen mode Exit fullscreen mode

Set up Amplify Storage

(Jump down to Implementing the Nextjs Image if your image is already hosted and you have the URL)

Install the aws-amplify package:

yarn add aws-amplify
Enter fullscreen mode Exit fullscreen mode

Next, initialize the Amplify backend on the project. Run this command on your integrated terminal

# Amplify CLI requireed. See prerequisites above
amplify init
Enter fullscreen mode Exit fullscreen mode

This command will also create a src/aws-exports.js file in the root directory. Go to pages/_app.js and modify the code to this:


    import { Amplify } from 'aws-amplify';
    import awsconfig from '../src/aws-exports';
    import Layout from '../src/components/Layout';

    Amplify.configure({
      ...awsconfig, ssr: true
    });
    function MyApp({ Component, pageProps }) {
      return (      
        <Component {...pageProps} />
      )
    }
    export default MyApp
Enter fullscreen mode Exit fullscreen mode

When this is done, run this command to add Amplify Storage.

amplify add storage
Enter fullscreen mode Exit fullscreen mode

You’ll be prompted with the following options to select

? Select from one of the below mentioned services: (Use arrow keys)
❯ Content (Images, audio, video, etc.)
    NoSQL Database
Enter fullscreen mode Exit fullscreen mode

Choose Content (Images, audio, video, etc.).

? You need to add auth (Amazon Cognito) to your project in order to add storage for user files. Do you want to add auth now? (Y/n)
Enter fullscreen mode Exit fullscreen mode

Choose Yes

? Provide a friendly name for your resource that will be used to label this category in the project: › nextimageresource
? Provide bucket name: › nextimageproject365
Enter fullscreen mode Exit fullscreen mode

You can use any name of your choice.

? Who should have access: …  (Use arrow keys or type to filter)
    Auth users only
❯ Auth and guest users
Enter fullscreen mode Exit fullscreen mode

You want Auth and guest users to have access to your Storage bucket.

? What kind of access do you want for Authenticated users? …  (Use arrow keys or type to filter)
    ✔ create/update
    ✔ read
❯✔ delete
Enter fullscreen mode Exit fullscreen mode

We want to give Auth users all the access they need.

? What kind of access do you want for Guest users? …  (Use arrow keys or type to filter)
    ✔ create/update
    ✔ read
❯✔ delete
Enter fullscreen mode Exit fullscreen mode

Also, give full access to unauthenticated users (for this article).

Finally,

✔ Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) · no
Enter fullscreen mode Exit fullscreen mode

Choose no

Let’s now go ahead and push this to the cloud. Run this command:

amplify push
Enter fullscreen mode Exit fullscreen mode

When this is done, run this command on your terminal to open your project dashboard on Amplify.

amplify console

? Which site do you want to open? …  (Use arrow keys or type to filter)
❯ Amplify Studio
    AWS console
Enter fullscreen mode Exit fullscreen mode

Choose Amplify Studio. This will open the studio on your default browser.

Uploading an Image

Click on the File browser link in the left sidebar.

Click on the public folder. All files here will be accessible by anybody that uses your app. Go ahead and upload an image.

Click on the checkbox and copy the JavaScript code at the bottom ride sidebar. This code snippet returns the full image URL with appropriate permissions.

Head to next.config.js and update it with these lines of code:

module.exports = {
    reactStrictMode: true,
    images: {
    remotePatterns: [
        {
        protocol: 'https',
        hostname: '[bucketname-dev].s3.amazonaws.com',
        port: '',
        pathname: '/public/**',
        },
    ],
    },
}
Enter fullscreen mode Exit fullscreen mode

Replace [bucketname-dev] with your bucket name. It should end with -dev.

Retrieving the Image from Storage

To retrieve the image we just uploaded, update the index.js with these lines of code.

// code snippet from https://github.com/Djcodebeast/next-amplify-image/blob/main/pages/index.js
import { Storage } from 'aws-amplify';
import { useState, useEffect } from 'react'
...
const [images, getImages] = useState([])
const getUploadedImage = async () => {
    const file = await Storage.get("pexels-marina-leonova-9465701.png", {
        level: "public"
    });
    getImages(file)
}
useEffect(() => {
    getUploadedImage()
}, [])
...
Enter fullscreen mode Exit fullscreen mode

The get() method accepts two arguments; the first one (not optional) is the prefix. For instance, if you uploaded the images to a photos folder in the s3 bucket, the prefix will be /photos. The other argument is an object that can accept a range of properties, including the level of protection.

Let’s render the image using Next Image component.

<div>
    <Image
    src={images}
    alt="Picture of the author"
    width={300}
    height={300}
    unoptimized={true}
    />
</div>
Enter fullscreen mode Exit fullscreen mode

This is an unoptimized image. It’s equivalent to the <img /> element. Let’s go ahead and implement the features we discussed earlier.

Implementing the Nextjs Image Optimization features

Fill dimensions of the parent element:

Code snippet

<div className="relative w-[300px] h-[500px] border-2 border-red-400 my-8 overflow-hidden">
    <Image
        alt="Layout Fill"
        src={imageUrl}
        fill
        style={{
            objectFit: 'cover',
        }}
    />
</div>
<div className="relative w-[300px] h-[500px] border-2 border-red-400 my-8">
    <Image
        alt="Layout Fill"
        src={imageUrl}
        fill
        style={{
            objectFit: 'contain',
        }}
    />
</div>

<div className="relative w-[300px] h-[500px] border-2 border-red-400 my-8">
    <Image
        alt="Layout Fill"
        src={imageUrl}
        quality={100}
        fill
        style={{
            objectFit: 'none',
        }}
    />
</div>
Enter fullscreen mode Exit fullscreen mode

Blur-up placeholder
This will add a blur placeholder while the image is loading. To generate a data URL for an image, you can use a generator tool like this.

Code snippet

<Image
    alt="Placeholder Blur"
    src={imageUrl}
    placeholder="blur"
    blurDataURL="data:application/xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48RXJyb3I+PENvZGU+SHR0cFZlcnNpb25Ob3RTdXBwb3J0ZWQ8L0NvZGU+PE1lc3NhZ2U+VGhlIEhUVFAgdmVyc2lvbiBzcGVjaWZpZWQgaXMgbm90IHN1cHBvcnRlZC48L01lc3NhZ2U+PFJlcXVlc3RJZD43MkQ4NUVCQkMxQjg3QUVGPC9SZXF1ZXN0SWQ+PEhvc3RJZD5FdWxFc05sTWVLYnBHNStSVlc1bWFFTWlENzJNQ1pCTW8zbytGWmJuVnBYVVJrV1RQZkxoZC9iSWpoa0pUWDJ3czBOSVJQQVcyNGY1U3BwdUNEVkQwK25qQVkvbDNsVDQ8L0hvc3RJZD48L0Vycm9yPg=="
    width={700}
    height={475}
    style={{
        maxWidth: '100%',
        height: 'auto',
    }}
/>
Enter fullscreen mode Exit fullscreen mode

Responsive Image

Code snippet

<Image
    alt="layout Responsive"
    src={imageUrl}
    width={700}
    height={475}
    sizes="(max-width: 768px) 100vw,(max-width: 1200px) 50vw, 33vw"
    style={{
        width: '100%',
        height: 'auto',
    }}
/>
Enter fullscreen mode Exit fullscreen mode

Text on a background image

Code snippet

<Image
    alt="Blur Background"
    src={imageUrl}
    placeholder="blur"
    blurDataURL={`data:application/xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48RXJyb3I+PENvZGU+SHR0cFZlcnNpb25Ob3RTdXBwb3J0ZWQ8L0NvZGU+PE1lc3NhZ2U+VGhlIEhUVFAgdmVyc2lvbiBzcGVjaWZpZWQgaXMgbm90IHN1cHBvcnRlZC48L01lc3NhZ2U+PFJlcXVlc3RJZD43MkQ4NUVCQkMxQjg3QUVGPC9SZXF1ZXN0SWQ+PEhvc3RJZD5FdWxFc05sTWVLYnBHNStSVlc1bWFFTWlENzJNQ1pCTW8zbytGWmJuVnBYVVJrV1RQZkxoZC9iSWpoa0pUWDJ3czBOSVJQQVcyNGY1U3BwdUNEVkQwK25qQVkvbDNsVDQ8L0hvc3RJZD48L0Vycm9yPg==`}
    className="fixed min-h-screen max-w-[100vw] overflow-hidden z-[-1]"
    quality={100}
    fill
    style={{
        objectFit: 'cover',
    }}
/>
<p className="m-0 text-2xl leading-10 text-center pt-[40vh] text-white drop-shadow-lg">
    Image Component
    <br />
    as a Background
</p>
Enter fullscreen mode Exit fullscreen mode

Deploy App to Amplify

Amplify Hosting supports all Next.js features, including the new Next.js 13 features. Let’s walk through the steps to deploy your app.

Ensure you’ve created and pushed your code to GitHub or your preferred Git provider.

Navigate to your AWS console and the amplify dashboard for the project we created.

Click on the Hosting environments tab.

Next, choose your Git provider (Github, in my case) and click on the Connect branch button. Ensure you install and authorize AWS Amplify to access your repositories.

Click on the Next button.
You’ll be redirected to this page.

Choose dev as Environment and click on the Create new role.
You’ll be redirected to an IAM screen in the next tab. Click on the Next: Permissions button. Do the same for the rest of the screens.

Click on the Create role button.
Head back to the Build Settings tab.

Select the **amplifyconsole-backend-role** you just created. If you can’t find it, click on the refresh button.

Review and click on Save and deploy.

Sit back and watch your app get deployed. This might take a few minutes, depending on your internet connection.

Conclusion

In this article, we learned about Nextjs Image optimization features and using Amplify Storage. We also learned how to upload and retrieve Storage bucket objects programmatically.
Then we deployed our Nextjs 13 frontend and Amplify backend to Amplify hosting.

Top comments (2)

Collapse
 
tombray profile image
Tom Bray

Thanks for writing this!

I got this working but for some reason the optimized image is being generated on every request instead of being served from cache. I have minimumCacheTTL: 120 set in my next.config.js and I see "cache-control: public, max-age=120, must-revalidate
" in the response headers but a MISS for cloudfront and nextjs-cache.

Any ideas?

Collapse
 
rozv profile image
Rosalyn Poort

Is there a way to get images at build time in getstaticprops?