Last updated

Hosted microcopy with Asset Delivery API

This article describes how hosted microcopy work in Flex Template for Web (FTW).

Table of Contents

Starting from 2022-05, FTW template microcopy can be managed both in the built-in microcopy files and in Flex Console. This article describes how the FTW template uses the hosted microcopy and merges it with the built-in microcopy

Note: If you want to implement this feature into your pre-v8.5 FTW-daily template, you can see the necessary modifications in the PRs for ftw-daily. Read more:

In FTW-hourly, hosted microcopy are available in v10.5. In FTW-product, they are available in v9.2.

Hosted microcopy

Hosted microcopy is microcopy that the marketplace operator can edit in Flex Console. The client app then needs to fetch it using the Asset Delivery API.

It is good to note that even if the operator adds some hosted microcopy using Flex Console, the FTW template still needs to have a built-in microcopy file for the microcopy keys that do not have a value in the hosted asset. That way, the UI can still render something meaningful for the parts of the page that the operator has not modified.

FTW templates have specified hosted microcopy as part of the app-wide configuration in src/config.js. This hosted microcopy lives in a file called content/translations.js, since language-specific microcopy files make it fairly easy to translate the FTW template to languages other than the default English.

// CDN assets for the app. Configurable through Flex Console.
// Currently, only translation.json is available.
const appCdnAssets = {
  translations: 'content/translations.json',
};

In addition, FTW templates have added a new global Redux file (src/ducks/hostedAssets.duck.js), which exports a Redux Thunk function called fetchAppAssets. This is the function that actually makes the calls to the Asset Delivery API.

There are two ways to fetch microcopy assets using Asset Delivery API: by version or by alias.

Fetching microcopy by version

All assets are identifiable by their version, and versions are immutable. Therefore if you fetch assets by version, they can be cached for an extended period of time. Read more about caching assets. When fetching microcopy by version, it is cached for an extended period of time, which helps to avoid unnecessary data loading. Since Asset Delivery API sets Cache-Control header for these responses, the browser knows to cache these responses on its own.

Hosted assets are versioned as a whole asset tree - a bit similar to how Git works. Individual asset files might have not changed when the whole version has changed and this might cause HTTP redirects. Since FTW templates use Flex SDK, the response always contains the data for the requested asset, even if the asset has not changed in the specified version.

sdk.assetByVersion({
  path: 'content/translations.json',
  version: '<some-hash-string>',
});

Fetching microcopy by alias

In addition to fetching assets by version, you can fetch them by a specific alias instead of a version. Currently, microcopy can be fetched with the alias latest, which returns the most recently updated version of the microcopy file. The response also contains the version information for the most recent asset, so that subsequent fetches can be done based on asset version.

When fetching by alias, the cache time is a few seconds for development environment and up to 5 minutes for production environment. In other words, it can take up to 5 minutes for microcopy updates to be visible in a production environment. These cache times are subject to change.

sdk.assetByAlias({
  path: 'content/translations.json',
  alias: 'latest',
});

How production build works with hosted microcopy

This setup is in use if you run yarn start in your host environment or yarn run dev-server on your local machine.

  1. Export fetchAppAssets from src/index.js to make it available for the server.

  2. SSR: initialize the store

    └── server
        └── dataLoader.js
    let translations = {};
    const store = configureStore({}, sdk);
    return store.dispatch(fetchAppAssets(config.appCdnAssets));
  3. SSR: fetchAppAssets thunk fetches the latest version of content/translations.json asset by using latest alias with sdk.assetByAlias

    • This ensures that the server-side rendering has the most recent version of asset to render the page
  4. SSR: make a loadData call if the route specifies it and

      .then(fetchedAssets => {
        translations = fetchedAssets?.translations?.data || {};
        return Promise.all(dataLoadingCalls);
    })
  5. SSR: asset version is saved to the store and passed to the browser through the preloadedState.

    • Server-side rendering must match with client-side rendering when browser hydrates the server-side rendered content.
    • To ensure that client-side rendering has the same version of microcopy, the asset version is passed (through preloaded state) to front end.
    • Note: FTW does not pass the microcopy itself from the server to browser. The reason is that if browser fetches the versioned asset file directly, it can leverage browser's cache. So, every page load, after the initial one, will use the translation.json file from the browser's local cache.
      .then(() => {
        return { preloadedState: store.getState(), translations };
      })
  6. SSR: call renderApp function, which renders the <ServerApp> in src/app.js

    • Hosted microcopy from Asset Delivery API is passed as props to ServerApp.
    └── server
        └── renderer.js
    // Render the app with given route, preloaded state, hosted microcopy.
    const { head, body } = renderApp(
      requestUrl,
      context,
      preloadedState,
      translations,
      collectWebChunks
    );
    └── src
        └── app.js
    <ServerApp
      url={url}
      context={serverContext}
      helmetContext={helmetContext}
      store={store}
      hostedTranslations={hostedTranslations}
    />
  • Hosted microcopy is then merged with the default microcopy in ServerApp component.
    export const ServerApp = props => {
      const { url, context, helmetContext, store, hostedTranslations = {} } = props;
      setupLocale();
      HelmetProvider.canUseDOM = false;
      return (
        <IntlProvider
          locale={config.locale}
          messages={{ ...localeMessages, ...hostedTranslations }}
          textComponent="span"
        >
      {/* etc. */}
  1. Browser: initialize the store with a preloaded state
    • The asset version that the SSR used, is included in that preloaded state
  2. Browser: fetchAppAssets thunk fetch assets using asset version: calls sdk.assetByVersion.
  3. Browser: make loadData call
  4. Browser: hydrate the and pass microcopy as props.
  • Hosted microcopy is merged with default microcopy in ClientApp component.
    <ClientApp store={store} hostedTranslations={translations} />

How development build works with hosted microcopy

This is setup is in use if you run yarn run dev on local machine.

  1. Browser: initialize the store
  2. Browser: fetchAppAssets thunk fetches assets using latest alias by calling sdk.assetByAlias
    • Because SSR is not available to fetch the latest version of the asset files, this call needs to be made from browser.
  3. Browser: make loadData call
  4. Browser: render the <ClientApp> and pass microcopy as props.

Read more

If you want to read more, here are some pointers: