Optimized images with AEM Headless images-with-aem-headless
Images are a critical aspect of developing rich, compelling AEM headless experiences. AEM Headless supports management of image assets and their optimized delivery.
Content Fragments used in AEM Headless content modeling, often reference image assets intended for display in the headless experience. AEM’s GraphQL queries can be written to provide URLs to images based on where the image is referenced from.
The ImageRef
type has four URL options for content references:
_path
is the referenced path in AEM, and does not include an AEM origin (host name)_dynamicUrl
is the URL to for image asset’s web-optimized delivery.- The
_dynamicUrl
does not include a AEM origin, so the domain (AEM Author or AEM Publish service) must be provided by the client application.
- The
_authorUrl
is the full URL to the image asset on AEM Author- AEM Author can be used to provide a preview experience of the headless application.
_publishUrl
is the full URL to the image asset on AEM Publish- AEM Publish is typically where the production deployment of the headless application displays images from.
The _dynamicUrl
is the recommended URL to use for image asset delivery and should replace the use of _path
, _authorUrl
, and _publishUrl
whenever possible.
Content Fragment Model
Ensure the Content Fragment field containing the image reference is of the content reference data type.
Field types are reviewed in the Content Fragment Model, by selecting the field, and inspecting the Properties tab on the right.
GraphQL persisted query
In the GraphQL query, return the field as the ImageRef
type, and request the _dynamicUrl
field. For example, querying an adventure in the WKND Site project and including image URL for the image asset references in its primaryImage
field, can be done with a new persisted query wknd-shared/adventure-image-by-path
defined as:
query($path: String!, $imageFormat: AssetTransformFormat=JPG, $imageSeoName: String, $imageWidth: Int, $imageQuality: Int) {
adventureByPath(
_path: $path
_assetTransform: {
format: $imageFormat
width: $imageWidth
quality: $imageQuality
preferWebp: true
}
) {
item {
_path
title
primaryImage {
... on ImageRef {
_dynamicUrl
}
}
}
}
}
Query variables
{
"path": "/content/dam/wknd-shared/en/adventures/bali-surf-camp/bali-surf-camp",
"imageFormat": "JPG",
"imageWidth": 1000,
}
The $path
variable used in the _path
filter requires the full path to the content fragment (for example /content/dam/wknd-shared/en/adventures/bali-surf-camp/bali-surf-camp
).
The _assetTransform
defines how the _dynamicUrl
is constructed to optimize the served image rendition. Web-optimized images URLs can also be adjusted on the client by changing the URL’s query parameters.
format
GIF
, PNG
, PNG8
, JPG
, PJPG
, BJPG
, WEBP
, WEBPLL
, WEBPLY
seoName
-
, or _
crop
size
rotation
R90
, R180
, R270
flip
HORIZONTAL
, VERTICAL
, HORIZONTAL_AND_VERTICAL
quality
width
size
is provided width
is ignored.preferWebP
true
and AEM serves a WebP if the browser supports it, regardless of the format
.true
, false
GraphQL response
The resulting JSON response contains the requested fields containing the web-optimized URL to the image assets.
{
"data": {
"adventureByPath": {
"item": {
"_path": "/content/dam/wknd-shared/en/adventures/bali-surf-camp/bali-surf-camp",
"title": "Bali Surf Camp",
"primaryImage": {
"_dynamicUrl": "/adobe/dynamicmedia/deliver/dm-aid--a38886f7-4537-4791-aa20-3f6ef0ac3fcd/adobestock_175749320.jpg?preferwebp=true&width=1000&quality=80"
}
}
}
}
}
To load the web-optimized image of the referenced image in your application, used the _dynamicUrl
of the primaryImage
as the image’s source URL.
In React, displaying a web-optimized image from AEM Publish looks like:
// The AEM host is usually read from a environment variable of the SPA.
const AEM_HOST = "https://publish-p123-e456.adobeaemcloud.com";
...
let dynamicUrl = AEM_HOST + data.adventureByPath.item.primaryImage._dynamicUrl;
...
<img src={dynamicUrl} alt={data.adventureByPath.item.title}/>
Remember, _dynamicUrl
does not include the AEM domain, so you must provide the desired origin for the image URL to resolve.
Responsive URLs
The above example shows using a single size image, however in web experiences, responsive image sets are often required. Responsive images can be implemented using img srcsets or picture elements. The following code snippet shows how to use the _dynamicUrl
as a base. width
is a URL parameter that you can then append to _dynamicUrl
for powering different responsive views.
// The AEM host is usually read from a environment variable of the SPA.
const AEM_HOST = "https://publish-p123-e456.adobeaemcloud.com";
...
// Read the data from GraphQL response
let dynamicUrl = AEM_HOST + data.adventureByPath.item.primaryImage._dynamicUrl;
let alt = data.adventureByPath.item.title;
...
{/*-- Example img srcset --*/}
document.body.innerHTML=`<img>
alt="${alt}"
src="${dynamicUrl}&width=1000}"
srcset="`
${dynamicUrl}&width=1000 1000w,
${dynamicUrl}&width=1600 1600w,
${dynamicUrl}&width=2000 2000w,
`"
sizes="calc(100vw - 10rem)"/>`;
...
{/*-- Example picture --*/}
document.body.innerHTML=`<picture>
<source srcset="${dynamicUrl}&width=2600" media="(min-width: 2001px)"/>
<source srcset="${dynamicUrl}&width=2000" media="(min-width: 1000px)"/>
<img src="${dynamicUrl}&width=400" alt="${alt}"/>
</picture>`;
React example
Let’s create a simple React application that displays web-optimized images following responsive image patterns. There are two main patterns for responsive images:
- Img element with srcset for increased performance
- Picture element for design control
Img element with srcset
Img elements with srcset are used with the sizes
attribute to provide different image assets for different screen sizes. Img srcsets are useful when providing different image assets for different screen sizes.
Picture element
Picture elements are used with multiple source
elements to provide different image assets for different screen sizes. Picture elements are useful when providing different image renditions for different screen sizes.
Example code
This simple React app uses the AEM Headless SDK to query AEM Headless APIs for an Adventure content, and displays the web-optimized image using img element with srcset and picture element. The srcset
and sources
use a custom setParams
function to append the web-optimized delivery query parameter to the _dynamicUrl
of the image, so change the image rendition delivered based on the web client’s needs.
Querying against AEM is performed in the custom React hook useAdventureByPath that uses the AEM Headless SDK.
// src/App.js
import "./App.css";
import { useAdventureByPath } from './api/persistedQueries'
const AEM_HOST = process.env.AEM_HOST;
function App() {
/**
* Update the dynamic URL with client-specific query parameters
* @param {*} imageUrl the image URL
* @param {*} params the AEM web-optimized image query parameters
* @returns the dynamic URL with the query parameters
*/
function setOptimizedImageUrlParams(imageUrl, params) {
let url = new URL(imageUrl);
Object.keys(params).forEach(key => {
url.searchParams.set(key, params[key]);
});
return url.toString();
}
// Get data from AEM using GraphQL persisted query as defined above
// The details of defining a React useEffect hook are explored in How to > AEM Headless SDK
// The 2nd parameter define the base GraphQL query parameters used to request the web-optimized image
let { data, error } = useAdventureByPath(
"/content/dam/wknd-shared/en/adventures/bali-surf-camp/bali-surf-camp",
{ imageFormat: "JPG" }
);
// Wait for AEM Headless APIs to provide data
if (!data) { return <></> }
const alt = data.adventureByPath.item.title;
const imageUrl = AEM_HOST + data.adventureByPath.item.primaryImage._dynamicUrl;
return (
<div className="app">
<h1>Web-optimized images</h1>
{/* Render the web-optimized image img with srcset for the Adventure Primary Image */}
<h2>Img srcset</h2>
<img
alt={alt}
src={setOptimizedImageUrlParams(imageUrl, { width: 1000 })}
srcSet={
`${setOptimizedImageUrlParams(imageUrl, { width: 1000 })} 1000w,
${setOptimizedImageUrlParams(imageUrl, { width: 1600 })} 1600w,
${setOptimizedImageUrlParams(imageUrl, { width: 2000 })} 2000w`
}
sizes="calc(100vw - 10rem)"/>
{/* Render the web-optimized picture for the Adventure Primary Image */}
<h2>Picture element</h2>
<picture>
{/* When viewport width is greater than 2001px */}
<source srcSet={setOptimizedImageUrlParams(imageUrl, { width : 2600 })} media="(min-width: 2001px)"/>
{/* When viewport width is between 1000px and 2000px */}
<source srcSet={setOptimizedImageUrlParams(imageUrl, { width : 2000})} media="(min-width: 1000px)"/>
{/* When viewport width is less than 799px */}
<img src={setOptimizedImageUrlParams(imageUrl, { width : 400, crop: "550,300,400,400" })} alt={alt}/>
</picture>
</div>
);
}
export default App;