Migrate from Resizer v1 to v2
Introduction to Resizer v2
Photo Center stores the highest resolution of an image that it can, so it’s important to always use the Arc XP Resizer to optimize the image for web display. For example, an image shown next to a story in a search engine results page should be thumbnail sized so that the page loads quickly, or sometimes images need to have a consistent shape, regardless of their original aspect ratios.
Rather than store many versions of the same image, Arc XP uses a just-in-time image resizing service to create resized or manipulated images as needed, which are called transformations. The resized images are automatically cached in Arc XP and are distributed using our Content Delivery Network to ensure the fastest page loading time possible.
Arc XP has moved to a newer version of Resizer (v2) as part of the continuous improvement efforts of our global SaaS platform. Key changes include moving to a more powerful and reliable option resizing service and the ability for you to add SEO-friendly names for images, which can help your site’s SEO performance. Additionally, with the new Resizer, the authentication token is included in the image ANS and is good for all resize variations, so you won’t have to worry about storing the secret key.
Background
Why This Document Matters
You are wondering what your organization needs to do to ensure that all current and future resized images are migrated to using Resizer v2.
When it Needs to Get Done
January 2023 - November 2023
Target Audience for This Guide
- Developer, technically-savvy
- Worked with the current Arc XP Resizer and/or is familiar with the customer repos
Resources
Overview
Arc XP now includes the path /resizer/v2/
as the entryway into Arc XP’s new resizing solution: Resizer v2. Resizer v2 is easier to use, faster, and more reliable than v1.
Instead of needing to generate a token for each resize variation, users need only one token per source image to authenticate all resize variations. This token appears in the auth
field of image ANS or is generated on-demand through Signing Service, an API that generates these tokens. Photo Center takes care of calling Signing Service for all images flowing through the system. This means that secret keys no longer need to be shared with clients and stored in their front ends.
Secondly, the resize options live in query parameters instead of the path itself. Both of these changes mean that there is no longer a need for having third-party libraries or packages to generate resize URLs.
Another major improvement in Resizer v2 is the ability to have human-readable filenames in resize URLs so that Google SEO ranking on those images is improved. Photo Center added a new UI field, SEO Filename
, which, if populated, saves as slugified text and can be prepended to the Arc ID in a resize URL.
SEO Performance Impact
First Hit
First hit is defined as fetching the pristine image and creating the transformed image (derivative) based on the transformation request.
Migrating to Resizer v2 impacts your SEO performance because moving from v1 to v2 requires images to be re-fetched for the first time, transformed at edge, and cached, resulting in unavoidable latency.
Migrations do not impact your monthly bandwidth or storage billing.
Migration aside, first hit of Resizer v2 is slower (more latent) than Resizer v1. However, the pros outweigh this single con that may or may not actually affect your SEO performance. Resizer v2 provides the following benefits:
- More stability than Resizer v1
- Better turnaround time when it comes to reported issues
- More efficient developer workflow for creating image derivatives
- More robust features that can be added in the future
Lastly, after the first hit impact, subsequent and cached hits perform much better and on par with v1.
Subsequent Hits
After a first hit, the various transformed images are cached. The latency of a cached image performs the same as in v1 (in terms of latency), and therefore does not impact your SEO performance. Based on our analysis with a beta partner, comparing v1 to v2 for both cached and uncached and for the whole site, we see that v1 and v2 are on par in terms of latency and size output performance. Note that it is important to do this analysis after giving v2 at least seven days to properly cache and run its course.
TIME | COUNT | LATENCY (ms) | SIZE (kb) | |
---|---|---|---|---|
v1 | Feb 6 - Feb 7 | 1.31M | 75ms | 27.3kb |
v2 | Oct 4 - Oct 5 | 947k | 75.9ms | 27.8kb |
v1 Image URLs
After you migrate to Resizer v2, it’s possible that v1 image URLs may still exist that Google bots are still crawling. Additionally, the migration may accidentally miss v1 image URLs. Resizer v2 is in General Availability on Production. We are adding a redirect so that v1 URLs automatically redirect to v2.
Non-Themes (Action Required)
To migrate from Resizer v1 to v2, follow this guide. For non-Themes clients, you may wish to launch your Resizer v2 implementation on a smaller section of your site in phases, rather than switching from Resizer v1 to v2 all at once. There are pros and cons to this method:
-
Pros
- Allows you to monitor your Resizer v2 implementation at scale for any errors
-
Cons
- Slower migration timetable
- More overhead work required from your development team
If you do wish to do a phased migration to Resizer v2 on a smaller section of your site, see Phased migration from Resizer v1 to Resizer v2 for more details.
Themes Blocks (Review if Action is Required)
Themes Blocks refers to Blocks that are out-of-the-box. If your organization also maintains Custom Blocks, refer to the Non-Themes section of this doc to migrate your custom blocks.
Image Resizer v2 is fully integrated with Themes 2.0. Therefore, after you upgrade your Themes Blocks to Themes 2.0, the images in all of those Blocks are fully migrated to Image Resizer v2 automatically. However, it is important to note specific image components and blocks deprecations that may require updating during your upgrade to Themes 2.0.
Using Resizer v2 with PageBuilder
This guide covers the following three use cases for Resizer v2 and PageBuilder:
- Using ANS content that already has the
auth
field for each image. - Using ANS content and inflating the object to include the
auth
field. This use case is for historical content that has not been migrated to have theauth
field back filled. - Using non-ANS content and generating the
auth
field.
Assumptions
- You are able to run PageBuilder locally and make changes to your feature bundle.
- You are familiar with content sources and either have custom content sources or are using Themes-provided content sources. See PageBuilder Content Source Documentation.
- You are familiar with PageBuilder Blocks and are able to update them.
- You are familiar with environment variables and how to add new variables. See PageBuilder Environment Variables Documentation.
- For the following examples, we are going to assume there is no image resizing being used for your code and you are rendering the original images.
For the following documentation, we are going to use the following custom block code that is using ANS content to render an image and headline. The image comes from the Promo Items of Composer, and the headline is the Composer headline.
import React from "react";import PropTypes from "@arc-fusion/prop-types";import { useContent } from "fusion:content";
const PromoBlock = ({ customFields }) => { const content = useContent({ source: customFields?.itemContentConfig?.contentService ?? null, query: customFields?.itemContentConfig?.contentConfigValues ? { ...customFields.itemContentConfig.contentConfigValues, } : null, }) || null;
if (!customFields?.itemContentConfig || !content) return null;
return ( <article> {/* The image is using Cloudfront directly - which is not the most optimized way to display an image from Photo Center Ensure images always go through resizer - see resizer v2 document for details */} <img src={content?.promo_items?.basic?.url} alt="" /> <h2>{content?.headlines?.basic}</h2> </article> );};
PromoBlock.propTypes = { customFields: PropTypes.shape({ itemContentConfig: PropTypes.contentConfig("ans-item").tag({ group: "Configure Content", label: "Display Content Info", }), }),};
PromoBlock.label = "Promo Block";PromoBlock.icon = "paragraph-bullets";
export default PromoBlock;
In the following items, we reference this code and detail how you would use Resizer v2 to update this custom block to gain performance improvements.
ANS content
Using ANS content with the auth
field
In the ideal situation, and for all new content entering the system, we can assume that when you retrieve an ANS object, such as a Composer story and images that are added to the story, it will have the necessary auth
field.
To update our previous example code to use Resizer v2, we need to update our img
source to reference Resizer v2. The updates we make to our code are:
- Add new environment variables for
- resizer path
- auth key
- Use the Resizer v2 path
/resizer/v2/
. Important note: For the Resizer v2 base path, thev
is case sensitive and should be lowercase- When developing locally, a full Resizer v2 path is required:
https://{CDN URL}/resizer/v2/
. Relative paths do not work locally.- The
{CDN URL}
is the public site URL that is associated with the environment in which the developer’s auth token was provisioned. If you need help retrieving this URL, contact your Technical Account Manager or Arc XP Client Support.
- The
- When developing locally, a full Resizer v2 path is required:
- Use the image
_id
and file extension - Use the
auth
field provided in the ANS data - Use Resizer v2 options to resize the image to a different size (width of 300px)
{ "RESIZER_TOKEN_VERSION": 1, "RESIZER_URL": "{CDN URL}/resizer/v2/"}
Now we can import them at the top of our block:
import { RESIZER_URL, RESIZER_TOKEN_VERSION } from "fusion:environment";
We can then update our component to match the following code:
const promoImage = content?.promo_items?.basic;
// Get the Image Auth Token from the `auth` object and use the// key assigned to auth key 1const imageAuth = promoImage.auth[RESIZER_TOKEN_VERSION];
// Get the _id of the Imageconst imageID = promoImage._id;
let assetId = imageID;
// Append the file extension of the image to assetId if presentif (promoImage.url.split("/").pop().split(".").length > 1) { assetId = assetId.concat(".", promoImage.url.split(".").pop());}
return ( <article> <img src={`${RESIZER_URL}/${assetId}?auth=${imageAuth}&width=300`} alt="" /> <h2>{content?.headlines?.basic}</h2> </article>);
As you can now see, the img src
has been updated to not use the raw image URL. It is now using Resizer v2 with its auth token and resizer options.
Inflating ANS objects
In some cases, the auth
field may not be present in the ANS response. This would be true for historical content if you’ve been an Arc XP customer before Resizer v2 was released in January 2023.
In the situation where you’re dealing with ANS, and you’re unsure if the auth
field will be present, you must “inflate” the ANS object with an auth
field by using the signing service (see Arc XP Signing Service API).
To achieve this in the most performant way, we make use of a feature from PageBuilder Engine 3.1 called partial caching. If you are unfamiliar with partial caching, we recommend reading the PageBuilder Engine documentation before continuing.
To inflate an object through a content source, you must:
- Have a content source to fetch data from the signing service
- Update existing content sources to
fetch
- Add additional logic to the content source to call the signing service content source when needed
Here’s an example content source for signing service:
import axios from "axios";import { ARC_ACCESS_TOKEN, CONTENT_BASE, SIGNING_SERVICE_DEFAULT_APP, RESIZER_TOKEN_VERSION,} from "fusion:environment";
const params = { id: "text", service: "text", serviceVersion: "text",};
const fetch = ({ id, service = SIGNING_SERVICE_DEFAULT_APP, serviceVersion = RESIZER_TOKEN_VERSION,}) => axios({ url: `${CONTENT_BASE}/signing-service/v2/sign/${service}/${serviceVersion}?value=${encodeURIComponent(id)}`, headers: { "content-type": "application/json", Authorization: `Bearer ${ARC_ACCESS_TOKEN}`, }, method: "GET", }).then(({ data: content }) => content);
export default { fetch, params, http: false, // 365 day ttl ttl: 31536000,};
The previous example of a content source for accessing the signing service uses environment variable to allow for scalability of using the server for other service types in the future. This requires the following environment variables to be added to your feature bundle.
{ "SIGNING_SERVICE_DEFAULT_APP": "resizer", "RESIZER_TOKEN_VERSION": 1}
The RESIZER_TOKEN_VERSION
value must match the currently enabled SSM version for your organization’s secret from the “Get HMAC Keys” from the Organization section in the Arc XP Delivery API.
Now that we have a signing service content source, we need to update our content sources that fetch ANS data from Arc XP APIs to inflate objects if needed. This could require many changes to your content source the resolve
pattern, as we have to switch to use the fetch
pattern.
You must include the ability to handle redirects in
fetch
content sources that are automatically handled byresolve
content sources in PageBuilder. For more information, see Handling redirects in content sources.
Here is an example of calling the Content API for a single item and inflating only the promo images image (Featured Media). ANS objects can contain multiple images, and these would also need inflating. You would need to update the following code to have this ability.
import axios from 'axios';import { CONTENT_BASE, ARC_ACCESS_TOKEN, RESIZER_TOKEN_VERSION,} from 'fusion:environment';
// Import the signing service content sourceimport signingService from './signing-service';
const params = { _id: 'text',};
const fetch = async ({ _id: id, 'arc-site': site }, { cachedCall }) => { const contentRequest = await axios({ url: `${CONTENT_BASE}/content/v4/?_id=${id}${site ? `&website=${site}` : ''}`, headers: { 'content-type': 'application/json', Authorization: `Bearer ${ARC_ACCESS_TOKEN}`, }, method: 'GET', }).then(({ data: content }) => content);
// Inflate ANS with token promo items -> basic -> image if there is no auth if (contentRequest.promo_items.basic.type === 'image' && !contentRequest.promo_items.basic?.auth?.[RESIZER_TOKEN_VERSION]) { contentRequest.promo_items.basic.auth = contentRequest.promo_items.basic.auth || {}; const signingResponse = await cachedCall( 'image-token', signingService.fetch, // The fetch method imported from the resizer content source { query: { id: contentRequest.promo_items.basic._id }, ttl: 31536000, independent: true }, ); contentRequest.promo_items.basic.auth[RESIZER_TOKEN_VERSION] = signingResponse.hash; }
return { ...contentRequest, };};
export default { schemaName: 'ans-item', params, fetch,};
The previous code is doing the following:
- Importing the signing service content source
- The code assumes the two content sources are in the same directory within your feature bundle
- Importing the
RESIZER_TOKEN_VERSION
environment variable allows us to inflate an auth object as we would see from content responses that do not require inflation - Using the
fetch
content source pattern with partial caching - Using
axios
to make the request to the content API - With the response from Content API, we are:
- Checking if the
promo_items.basic.type
is an image - If so, we make use of the partial caching feature to make a request to the signing service using the signing service content source
- Passing the image
_id
to the signing service to get a signed string for the image_id
- The response of the signing service content source is added to the content API response in the same pattern as the Content API would by assigning the signing service response to an
auth
object using the version number as the object key
- Checking if the
Now with the content source updated to inflate the ANS object with the auth
data, your blocks do not have to worry about getting auth
tokens, and you can make all blocks, expect the auth
token is provided for them. This is also more performant to handle the fetching of auth
tokens on the server, but making use of caching.
Resizer URL creation
To determine the Resizer URL for your Photo Center images, use these steps:
- Determine your Resizer URL.
- If your site has not yet launched, your Resizer URL follows this model
https://{org}-{default-website}-{env}.web.arc-cdn.net/resizer
. - If your site has launched, your Resizer URL is
{website name}/resizer
.
- If your site has not yet launched, your Resizer URL follows this model
- Add
/v2
to the base Resizer URL:{website name}/resizer/v2
. - Take the image ID from the CloudFront URL of the image and append the ID to the Resizer URL from step 2. You can find the CloudFront URL either in ANS or the Photo Center UI path for a published image.
- Example of image ID from CloudFront URL:
SMDVGH3HWRBMRGDZHZFNTCP6EU.jpg
- Example of Resizer URL with appended ID:
{website name}/resizer/v2/SMDVGH3HWRBMRGDZHZFNTCP6EU.jpg
- Example of image ID from CloudFront URL:
- Make a
GET
request to the Photo APIphoto/api/v2/{photoId}
. Take the Auth token from the image ANS auth field (associated with the image) and paste that as a query parameter at the end of the URL. For example:{website name}/resizer/v2/SMDVGH3HWRBMRGDZHZFNTCP6EU.jpg?auth=db02a059a34fd56e1041d5513ec2745a54239c848a8b
Non-ANS content
Resizer v2 not only supports resizing images from Photo Center, but it also supports external images. In the next example, we demonstrate a scenario in PageBuilder where we have a block that has the ability for an editor to specify an external image through a custom field. We show you how you can use image Resizer v2 to transform the image URL provided through a custom field in your block.
Starting with the following manual promo block example, we have a block that outputs an image and headline, provided that both are supplied through PageBuilder Editor. Note the image is just going to use the imageURL
provided with no resizing.
import React from "react";import PropTypes from "@arc-fusion/prop-types";
const ManualPromo = ({ customFields }) => { const { imageURL, headline } = customFields; if (!imageURL || !headline) return null;
return ( <article> <img src={imageURL} alt="" /> <h2>{headline}</h2> </article> );};
ManualPromo.propTypes = { customFields: PropTypes.shape({ imageURL: PropTypes.string, headline: PropTypes.string, }),};ManualPromo.label = "Manual Promo Block";ManualPromo.icon = "paragraph-bullets";
export default ManualPromo;
Based on the example, we have a manual promo block that isn’t using any resizer logic for the image to display. This is not very performant for the end user if the editor was to put the URL to an extremely large image. We’re going to update the block to make use of Resizer v2. In order to do so, we need to:
- Update the block to call the signing service content source (see previous steps on setting up a signing server content source)
- Update the image
src
to use the Resizer v2 URL, auth token, and resizer parameters
The update to the component is similar to that of the previous example. The main difference here is as we are not dealing with ANS or data coming from a content source. We need to generate an auth
token ourselves from the signing service. Because the signing service is a content source, it’s a pattern that should be familiar to you.
As with before, we need to import the RESIZER_URL
from our environment variables. Then we need to import the useContent
hook from fusion:content
to allow us to make content source calls within our block.
As the signing service is a content source, we can use the useContent
hook to call it as per other content sources. For this case, we need to pass the imageURL
from the custom fields to the id
query param of the content source call.
The next step is to update the image src
attribute to construct the image src
as needed for Resizer v2. As we are calling the signing service content source ourselves to get the auth
token for Resizer v2, we use content.hash
as the value for the auth
query param in the image. Also, keep in mind the entire imageURL
must be URL-encoded, including the protocol.
import React from "react";import PropTypes from "@arc-fusion/prop-types";import { RESIZER_URL } from "fusion:environment";import { useContent } from "fusion:content";
const ManualPromo = ({ customFields = {} }) => { const { imageURL, headline } = customFields; const content = useContent({ source: 'signing-service', query: imageURL ? { id: imageURL } : null, }) || null;
if (!imageURL || !headline) return null;
return ( <article> <img src={`${RESIZER_URL}/${imageURL}?auth=${content.hash}&width=300`} alt="" /> <h2>{headline}</h2> </article> );};
ManualPromo.propTypes = { customFields: PropTypes.shape({ imageURL: PropTypes.string, headline: PropTypes.string, }),};
ManualPromo.label = "Manual Promo Block";ManualPromo.icon = "paragraph-bullets";
export default ManualPromo;
Further Reading and Resources
To ensure you’re delivering the best experience and images possible to your end users, other optimizations exist for responsive images and, if needed, art-directed images.
Themes handles these optimizations for you through our blocks and components.
- https://web.dev/learn/design/responsive-images/
- https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
Example Resizer v2 URL:
Example image ANS:
{ "_id": "6UI63OCUJI5AW6IHRW46XWAAPU", "additional_properties": { ... }, "auth": { "1": "40b3b900866998ec98c4a286eef727080a10ac968d5eed7bd4a6a084511db6c1" }, ... "seo_filename": "person-with-red-ball", "url": "https://cloudfront-us-east-1.images.arcpublishing.com/6UI63OCUJI5AW6IHRW46XWAAPU.jpg", "type": "image"}