Client-side Templates for Server-Side API Campaigns

👍

This capability is coming soon, expected to be rolled out during October 2024

While working with server-side API, you control the rendering and front end of your campaigns. Usually, the frontend code is managed within your app. However, changes in code require a full deployment of your app, something that is not needed when using client-side campaigns.

With Dynamic Yield's Experience API, you can use a server-side API but still manage client-side code in Dynamic Yield. This enables you to make changes without full deployment and leverage predefined code from the Dynamic Yield template library.

A short overview of the process

You can create server-side campaigns with HTML, CSS and JS. When you call these campaigns, the response will include the variables value in a JSON format, but it will also include a token you can use to fetch the HTML, CSS and JS template. Before the page is loaded, you can group the HTML, CSS and JS template with the dynamic value, and prepare it in the server side, before you render it to the page.

Step 1: Create a Web Personalization server-side campaign with HTML, CSS and JS code

In Experience OS, create a campaign and populate the HTML, CSS, and JS code tabs with the desired campaign structure and definitions. See our user documentation to learn more.

Here's an example of the HTML code for a banner out-of-the-box template:

<a href="${Banner Click Through URL}" class="link_dy_hero_${dyVariationId}">
<div class="dy_hero_banner">
  <div class="dy_hero_banner_texts">
  <div class="dy_hero_banner_title">${Main Text}</div>
  <div class="dy_hero_banner_subtitle">${Secondary Text}</div>
  <div class="dy_hero_banner_button">${Button Text}</div>
</div>
  </div>
  </a>

You can incorporate variables in all three code tabs, and use the same variable in more than one tab at a time. Variables can be of the following types: Text, Image, Dropdown (Enum), Color-picker, Number, or User Data feed property.

Learn more about using variables.

Note that for API Recommendation campaigns, only a single recommendation loop is permitted (${#loop_name})

Step 2: Get access to the variation code in your Experience API response via template token

Like any other server-side campaign, call the Choose endpoint with the campaign's selector name. The response provides the contents for the campaign.

📌

Notes:

  • If you aren't familiar with the Choose endpoint, we recommend that you read about it before getting started.
  • While the request scope is generally for a whole page, including multiple campaigns, this explanation refers to a single variation for simplicity.
  • In Step 6 you can see how to handle the whole response.

In the response, the choices object includes a list of variations for the campaign. For every variation that includes HTML, CSS or JS code, a template object is returned with all the relevant information. Note that only API Custom Code campaigns support returning multiple variations and that you must handle how these multiple variations are consumed, as each variation (content and client-side template) is defined as a standalone.

The following is an example of a template object returned in the Choose response. You can see examples of full responses in the Choose API article.

"template": {
   "token": "7254fde3a29066501444e4bc3e3d8332",
   "files": {
      "html": true,
      "css": false,
      "js": true
   }
}

Step 3: Fetch the variation client-side template code from the CDN

Use the following URL structure to fetch the HTML, CSS or JS code:

static.dy-api.{region}/{sectionId}/{token}.{extension}.template

Use the following values:

  • {region}: Either com or eu, corresponding to the data center used to access Experience OS.
  • {sectionId}: A 7-digit number that is the ID of your Dynamic Yield section. You can find the ID on the Sites page in Experience OS.
  • {token}: A unique hash passed in the template.token attribute in the Choose response. This value changes every time you save changes to the template in Experience OS. Example of a token value:
    7254fde3a29066501444e4bc3e3d8332
  • {extension}: Either html, css, or js, according the file mapping.

For example:

static.dy-api.com/8768867/7254fde3a29066501444e4bc3e3d8332.js.template

🚧

We've marked the template as .template to make sure you understand that these are not executable HTML, CSS, or JavaScript files, but rather, include dynamic content variable placeholders waiting for you to set their values before execution.

The following is an example of a JavaScript function that fetches a specific client-side template file (HTML, CSS, JS):

const hostUS = "https://static.dy-api.com/";
const hostEU = "https://static.dy-api.eu/";
const sectionId = "8768867";
const extensions = ["html", "js", "css"];

const fetchApiTemplate = async (token, extension, sectionId) => {
  const url = '$(hostUS)/${sectionId}/${token}.${extension}.template'
  try {
    const response - await fetch(url);
    if (!response.ok) {
      throw new Error('HTTP error! status: ${response.status)');
    }
    return await response.text();
  } catch (error) {
    console.error("Error in fetchApiTemplate:", error);
  }
};

Step 4: Prepare an object from the data in the response, which is dynamic, per variation per user

To transform the template into usable code, you must do some data prepping. The data for populating is in the API response, but note that it varies between campaign types.

  • API Custom Code campaigns:
    • Content: The data for populating the variable placeholders is found under each variation in the payload.data object.
    • Engagement attributes: Under each Choice (correlating to a campaign) there is a decisionId. This value can serve several variations if the campaign is set to return multiple variations, and each variation has a decisionId. Use this attribute to report impressions and clicks.
  • API Recommendation campaigns:
    • Content: The data for populating the variable placeholders is found under each variation in the payload.data.custom object.
    • Product data mapping: A mapping of the feed data in the slots object, using the product feed column names and the returned variable names, to the variables inside the recommendations loop (${#recommendations}).  
      Here's an example of the payload.data.custom.TemplateProductData object:
      "TemplateProductData": { 
            "Url": "url",  
            "Image_url": "image_url",  
            "Currency Icon": "dy_display_price", 
             "price": "price", 
             "Name": "name"  
      }
      
    • Recommendation: As in any recommendation campaign response, the slots object returns the product feed data for the recommended products. This data is used to duplicate the recommendation loop and populate each slot’s variables as in the client-side template.
    • Engagement attribute: Make sure to use slotId, variationId, and decisionId to report impressions and slot clicks.

The following is an example of 2 functions that prep the data from the API response to an object that can be easily used by the mustache library to help assemble the final code. It includes a full response and output examples of this function.

const getTemplateProductData = (slot, templateProductData) => {
  const { productData, sku } = slot;
 
  const result = {
    slotId: slot.slotId
  };
  for (const variableName in templateProductData) {
    const productFieldName = templateProductData[variableName];
    result[variableName] = productFieldName === "sku" ? sku : productData[productFieldName];
  }
  return result;
};
 
 
const transformDataByType = (choice, variation) => {
  if (choice.type !== CHOICE_TYPES.RECS_DECISION) {
    return {
      ...variation.payload.data
    };
  }
 
  const { custom, slots } = variation.payload.data;
  return {
    ...custom,
    decisionId: choice.decisionId,
    Recommendation: slots.map(slot => getTemplateProductData(slot, custom.TemplateProductData))
  };
};

Step 5: Combine the template files with the response content

Each template is composed of 1 to 3 template files. The files hold the latest published and saved content of the correlating tabs in the template/variation editor in Experience OS. Each contains variable placeholders for the variable values returned in the JSON by wrapping each variable name with ${ before and } after. For example, given a variable called “var1” in the JSON, by replacing any ${var1} found in the template files you’ll create a valid client-side file. In addition, we have a loop indicator (that can be used once, in API recommendation campaigns only) to iterate the returned recommended items.

To bond the client-side code and the object we recommend using the mustache library that enables this very functionality and is available in many coding languages. The following is a function called "render" that takes the template files and injects the response data into the variation placeholder to convert the template files to valid HTML/CSS/JS files.

import Mustache from "mustache";
Mustache.tags = ["${", "}"];
 Mustache.escape = function (text) {
   return text;
 };
const render = (template, data) => {
  try {
    return Mustache.render(template, data);
  } catch (err) {
    return template;
  }
};

Step 6: Handle the full Choose response

We recommend that every page result in a single Choose API call gather all personalization content from Dynamic Yield. Therefore, you must handle the entire response and take the needed steps either as listed in Step 5 for campaigns with client-side templates or using any other logic based on the response. The following code example contains two functions:

  • handleChoices: Iterates the choice object that's the main part of the API response containing a list of "choice" objects, each correlating to a campaign in Experience OS.
  • handleVariation: Returns a variation (a "choice" content) in its client-side format, including all files (see code examples in previous sections). Note that the following example handles only single-variation campaigns.
const handleVariation = async (choice, variation) => {
  if (!variation.template) {
    return null;
  }
 
  const { template: { files, token } } = variation;
  const transformedData = transformDataByType(choice, variation);
 
  const contents = {};
  const extensions = ["html", "js", "css"];
  for (const extension of extensions) {
    if (files[extension]) {
      const template = await fetchApiTemplate(token, extension);
      contents[extension] = render(template, transformedData);
    }
  }
 
  return {
    contents,
    data: transformedData
  };
};
 
export const handleChoices = async (choices) => {
  return await Promise.all(choices.map(async choice => {
    return {
      id: choice.id,
      name: choice.name,
      template: choice.variations.length > 0 ? await handleVariation(choice, choice.variations[0]) : null
    };
  }));
};

Step 7: Incorporate the code on your site

You should now have all the campaigns for the page assembled on the server side. The campaigns are in client-side format, meaning HTML, CSS and JS snippets that need to be incorporated on the page. Identify the stage in the server process where you already have handling parts or the entire page’s front-end code (HTML, CSS and JS) to create the most generic function that adds the campaigns code in the right places.

There are many ways to do this, and they vary depending on your stack, taking into consideration the ability to serve various type of campaigns in terms of allocation and incorporation within the codebase and the resources they use. Here are some things to consider when scoping and defining the function:

  • Content: What files are included in the campaign?
  • Allocation: Is the campaign replacing an existing element on the page? Should it go before or after an existing element? Are multiple variations returned? Should they be in a vertical or horizontal item list? Or maybe a slider?
  • Resources: Does the added CSS affect the whole page? Does the client-side template ue page resources? What is the availability of those resources?

A basic and easy solution is to incorporate the template's client-side code in the campaign’s location in the HTML by wrapping the CSS content in a

A basic and easy solution is to incorporate the template's client-side code in the campaign’s location in the HTML by wrapping the CSS content in a <style> tag, pasting the HTML content as-is and wrapping the script content (JS tab) in a <script> tag. The following is a server code example using this method to run locally for a site on which all its contents are API campaigns using client-side templates.

const http = require('http'); 
const templates = handleChoices(choices); 
const templatesContent = templates 
  .map(template => { 
    if (!template) return ''; // Skip if template is null or undefined      

    return ` 
      ${template.css ? `<style>${template.css}</style>` : ''} 
      ${template.html || ''} 
      ${template.js ? `<script>${template.js}</script>` : ''} 
    `; 
  }) 
  .filter(content => content.trim() !== '') // Remove any empty strings 
  .join(''); 

const server = http.createServer((req, res) => { 
  res.writeHead(200, {'Content-Type': 'text/html'}); 
  res.end(` 
    <!DOCTYPE html> 
    <html> 
    <head> 
      <meta charset="UTF-8"> 
      <title>Server-side Rendering Example</title> 
    </head> 
    <body> 
      ${templatesContent} 
    </body> 
    </html> 
  `); 
});  

const PORT = 3000; 
server.listen(PORT, () => { 
  console.log(`Server running at http://localhost:${PORT}/`); 
}); 

Step 8: Report engagement

Reporting engagement in this framework uses the Engagement Experience API endpoint. The main advantage is that you can add to your site a global function that handles the reporting with the data we recommended, to incorporate it into the element’s metadata. With this data you can easily report from the server or client side by calling the Engagement endpoint.