Skip to content
Product Documentation

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.

TIMECOUNTLATENCY (ms)SIZE (kb)
v1Feb 6 - Feb 71.31M75ms27.3kb
v2Oct 4 - Oct 5947k75.9ms27.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 the auth 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, the v 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.
  • 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 1
const imageAuth = promoImage.auth[RESIZER_TOKEN_VERSION];
// Get the _id of the Image
const imageID = promoImage._id;
let assetId = imageID;
// Append the file extension of the image to assetId if present
if (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 by resolve 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 source
import 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

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:

  1. 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.
  2. Add /v2 to the base Resizer URL: {website name}/resizer/v2.
  3. 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
  4. Make a GET request to the Photo API photo/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.

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"
}