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 settingwidth
andheight
. However, you must set aposition
property. By default, it’s set toposition: 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 ablur
value. When accessing a dynamic image, you have to set theblurDataURL
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 thesizes
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 theImage
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
When this is done, open the project in your code editor.
cd project-name
code . (for vs code users)
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
Next, initialize the Amplify backend on the project. Run this command on your integrated terminal
# Amplify CLI requireed. See prerequisites above
amplify init
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
When this is done, run this command to add Amplify Storage.
amplify add storage
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
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) ›
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
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
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
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
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
Choose no
Let’s now go ahead and push this to the cloud. Run this command:
amplify push
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
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/**',
},
],
},
}
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()
}, [])
...
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>
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:
<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>
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.
<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',
}}
/>
Responsive Image
<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',
}}
/>
Text on a background image
<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>
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)
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?
Is there a way to get images at build time in getstaticprops?