Hybrid Target - Part 1

16 Jun 2019 » Server Side

The next step after you have a hybrid ECID implementation is to do the same with Target. I already wrote a post on how to create a pure Adobe Target server-side implementation. Now I will explain how to create a hybrid implementation. This post will show the code and the next one, the Target configuration.

First page of the first visit

As I explained with the hybrid ECID, the first page of the first visit of a visitor is a special case. Since there is no AMVC cookie when a visitor first lands on your website, by the time the browser gets the ECID, the server has already executed all its code. Therefore, on this first page, you cannot use Target server-side. The only solution for this case is to execute Target client-side, which is why I call it a hybrid implementation.

If you are well versed in the technicalities of Adobe Target, you may be thinking that I am wrong. Technically you can call Target on all pages. What Target will do is generate a tntId and will use it as the visitor identification. So, if you are using Target in isolation (no ECID, no Adobe Analytics, no Audience Library), you do not need a hybrid implementation. You can user a pure Target server-side implementation, but keeping track of the tntId generated by Target to stitch calls.

Another case that you may want to explore is to not optimise nor personalise the first page of a visitor. As a developer, this will make your life easier, as you will not need the client-side code. Just make sure your friendly marketer is happy with this restriction. I am sure she will not be, but worth explaining pros and cons.

So, if you have to apply Target on this first page or have to integrate it with the rest of the Adobe Experience Cloud, read on.

Global vs regional mboxes

As of today, most Target implementations rely on the global mbox. This makes Target very easy to use and the visual experience composer (VEC) relies on it. The way it works is that it generates a set of instructions to manipulate the DOM. However, in a server-side world there is no DOM, as this is a browser concept. In theory, you could replicate the same behaviour server-side:

  1. In your CMS, generate the full HTML.
  2. Parse the HTML and generate a DOM server-side.
  3. Request the global mbox for this URL.
  4. Process the response from Target.
  5. Apply the changes instructed in the response to the DOM.
  6. Regenerate the HTML from the new DOM.
  7. Send the HTML to the browser.

Additionally, for the VEC to work, you will need to detect whether the page is being requested by Target and inject at.js. In this case, you should not run Target server-side (the previous steps).

I am not aware of any implementation that has done what I have just explained and it is probably advisable no to do so, due to its complexity and server CPU time it will require. If you have managed to make it work, please leave a comment, I would love to hear from you.

In summary, to make our lives simpler, I will use regional (or regular) mboxes from now on. These are just <div> element that we want to manipulate.

Invoking the Target API

Please refer to my old post for the details on how to invoke the Target API. You can also see the PHP Target class I created for the Summit. The most important remarks I have to say, are:

  • All my code is released AS-IS and it is not endorsed by Adobe. Furthermore, it is not production ready, it is just a PoC.
  • Use the “Server Side Batch Delivery” API. This is the latest version and, although it says “batch” in it, it is real time.
  • Remember to include the client’s IP address and User-Agent.
  • This API support HTTP 2.
  • Set the ECID, DCS location and blob from the ECID service in its corresponding parameters. Remember that the key names are not consistent.
  • You can pass an array of mboxes. Therefore, if you have multiple mboxes on the same page, you can request them in a single call.
  • You can even pre-fetch mboxes, if you think you are likely to need them, and later notify Target that you have actually used them.

The code

If you are a developer (as I was long time ago), this is probably the part you have been waiting for 🙂 I will use the sample index.php page I created for this purpose.

Initialisation

If the ECID service could not get the AMCV cookie, i.e., we are in the first page of the first visit, you cannot initialise Target:

/* Initialise Adobe Target */
$target = $ecid_available ? new Adobe\Target($config,$ecid,$data_layer['user_agent'],$data_layer['ip_client'],$data_layer['host']) : null;

Remember that this is server-side code, executing before the browser receives any HTML. As you can see, I am also passing the ECID object and the browser’s User-Agent and IP address to the Target object. If it could not be initialised, the $target object will be set to null. We will use this in the rest of the code.

HTML head

I know that I am repeating myself, but on the first page of the first visit, we cannot execute Target server-side. Instead, we need to inject the JavaScript Target code (at.js) to be able to execute it client-side. Therefore, while generating the HEAD portion of the HTML, you will have to add some additional code, something similar to:

<?php if (!$target) : ?>
  <!-- No ECID present; switching to client-side Target implementation -->
  <script type="text/javascript" src="path/to/at.js"></script>
  <script type="text/javascript">
    function targetPageParamsAll() {
      return {
        "at_property"	: "<?php echo $data_layer['at_property'] ?>",
      };
    }
    /* Pre-hiding snippet to avoid flicker; only hiding #banner */
    ;(function(win,doc,style,timeout){var STYLE_ID='at-mbox-prehide';function getParent(){return doc.getElementsByTagName('head')[0];}
    function addStyle(parent,id,def){if(!parent){return;}
    var style=doc.createElement('style');style.id=id;style.innerHTML=def;parent.appendChild(style);}
    function removeStyle(parent,id){if(!parent){return;}
    var style=doc.getElementById(id);if(!style){return;}
    parent.removeChild(style);}
    addStyle(getParent(),STYLE_ID,style);setTimeout(function(){removeStyle(getParent(),STYLE_ID);},timeout);}
    (window,document,"#banner {opacity: 0}",3000));
  </script>
<?php endif; ?>

A few things worth mentioning about this code:

  • The client-side code will only be present if there was no ECID. This is exactly what we want, so, from the 2nd page onwards, no client-side code will be generated.
  • I have removed the code for lab_user, as this was only for the Summit, but I have kept the Target property, as many implementation need it. You may want to add your own additional global mbox parameters.
  • This pre-hiding snippet only hides the <div id="banner"> element of the page, which is my mbox in this example. You probably will want to modify it to hide your mboxes.

Finally, the at.js file that we need must not create nor fire the global mbox. To do that, you need to edit the settings in Target:

And now you can download it.

Mbox code

The last piece is dealing with the mboxes. The pseudo-code is (remember this is what you would do server-side):

  1. If Target is initialised, request the mbox
  2. If an alternate content was retrieved, then paste it in the HTML; else, set default content
  3. If Target could not be initialised, add client-side code

The idea is that 2.b is executed if either Target could not be initialised or the mbox call returned nothing.

Now to my example (with a few minor changes from the Summit code):

<div id="banner">
<?php
  $mbox_name = 'my-mbox-name';
  $mbox_content = null;
  if ($target) {
    // Server-side implementation, when ECID is already present
    $mbox = new Adobe\Mbox($mbox_name);
    $mbox->setPageURL($data_layer['url']);
    $mbox->addParameter("at_property", $data_layer['at_property']);
    $target->request(array($mbox));
    $mbox_content = $mbox->getContent();
  }
  if ($mbox_content) : 
?>
        <?php echo $mbox_content ?>
<?php 
  else : // Default content
?>
        <img src="style/banner.jpg" />
<?php
  endif;
  if (!$target) :
?>
        <script type="text/javascript">
          adobe.target.getOffer({  
            "mbox": "<?php echo $mbox_name ?>",
            "success": function(offers) {
              adobe.target.applyOffer( { 
                "mbox": "<?php echo $mbox_name ?>",
                "selector": "#banner",
                "offer": offers 
              } );
	      document.getElementById("banner").style.opacity = "1";
            },  
            "error": function(status, error) {
              if (console && console.log) {
                console.log(status);
                console.log(error);
              }
	      document.getElementById("banner").style.opacity = "1";
            },
            "timeout": 5000
          });        
        </script>
<?php 
  endif; 
?>
      </div>

The only details I think require an explanation are:

  • This is the code for the mbox in <div id="banner">, which is the same element hidden by the pre-hiding snippet. If you have multiple mboxes, you will need to modify the pre-hiding snippet accordingly and add the client-side code to all of them. Feel free to optimise the way of inserting the code, as long as it suits you and no flicker is added.
  • Each mbox needs to have a unique name. In this example, I have named it my-mbox-name. Remember to generate your own naming convention around mbox names. If there are multiple mboxes on the page, each will require its own name.
  • I am using HTML offers in Target. This makes it very easy, as I can just paste the content coming in the mbox response and I can easily use adobe.target.getOffer() and adobe.target.applyOffer(). However, you may find other types of content types easier to manage, in which case, you will have to modify the code accordingly.
  • The line document.getElementById("banner").style.opacity = "1"; shows the pre-hidden element immediately after it has the final content, avoiding flicker.
  • The JavaScript code will only be inserted on the first page of the first visit. In the subsequent pages, the Target experience come directly in the HTML, with no need to control flicker.

Tag managers

You will have noticed that I am injecting always the code directly into the HTML, without the use of a tag manager. It is technically possible to use one, but you will need to configure it correctly so that it does not load or execute any code that is not needed. In other words, in the first page of a visitor, it will have to load the at.js and run the mbox calls. From then on, no additional JavaScript code should be present on the page.

However, I do not recommend it for 3 reasons:

  • The complexity it adds, as explained above.
  • One of the most important reasons to move to server-side implementation is to reduce the client-side code. Tag managers usually mean the opposite.
  • The idea of a server-side implementation is to control the code from the servers, for which you do not need a tag manager.


Related Posts