Two things make AEM and Adobe Analytics sing together. A clean data story and clear rules for eVars.
If you feel your reports fight you, it is usually because eVars were set in too many places with too many opinions.
I keep seeing the same pattern. Teams wire eVar numbers right inside AEM components, someone tweaks s_code in one place, DTM rules try to fix it in another, and the Adobe Analytics admin ends up chasing ghosts. We can do better with a small set of guardrails and a tidy data layer.
Today DTM is the main door to Adobe Analytics for most teams on AEM. AppMeasurement has replaced the old s_code loader for many sites. The good news is we can keep eVars clean without rewrites, as long as we agree on where numbers live and where names live.
Numbers or names first
Hard coding s.eVar12 in a component template feels quick. It is also the fastest way to lock your content to tool internals. I prefer sending context data with friendly keys and mapping those keys to eVars in Processing Rules. That keeps AEM focused on content and keeps Analytics focused on numbers.
The pattern looks like this. Components push rich names into a shared object. DTM reads those keys and sets s.contextData. Processing Rules map keys to eVars. If you need to move a key from eVar12 to eVar22, no code change in AEM and no change in DTM. Only the mapping moves.
// In a DTM Adobe Analytics rule or a custom script include
// Assume window.digitalData is filled by AEM on each view
if (window.digitalData) {
s.contextData['page.name'] = window.digitalData.page.pageName;
s.contextData['page.section'] = window.digitalData.page.section;
s.contextData['user.authState'] = window.digitalData.user.loggedIn ? 'y' : 'n';
s.contextData['campaign.code'] = window.digitalData.campaign.code || '';
s.t(); // page view
}
// Processing Rules in Adobe Analytics admin
// page.name -> eVar1
// page.section -> eVar2
// user.authState -> eVar5
// campaign.code -> eVar10
Map context data keys to eVars with Processing Rules. Keep the keys stable. Move only the mapping when reports change.
A tidy AEM data layer
AEM does not ship a data layer out of the box. You can still keep things tidy with a small digitalData object that follows the W3C style. Keep it predictable and flat where it matters. Let components contribute to it in the page footer or via a server render on the base template.
<script>
window.digitalData = {
page: {
pageName: "${currentPage.title}",
section: "${currentPage.properties['section'] || 'general'}",
template: "${currentPage.templateName}"
},
user: {
loggedIn: ${currentUser.loggedIn ? 'true' : 'false'},
id: "${currentUser.id || ''}"
},
campaign: {
code: "${request.getParameter('cid') || ''}"
},
events: [] // components push here
};
</script>
For component level clicks, let each component render a small payload on the node. Then listen once on the page and send s.tl calls with context data. This keeps tracking logic in one place and avoids re adding eVar numbers.
<div class="cmp-product-card"
data-analytics='{"product":{"id":"sku123","name":"Trail Boot"},"event":"productClick"}'>
... card markup ...
</div>
<script>
document.addEventListener('click', function(evt) {
var el = evt.target.closest('.cmp-product-card');
if (!el) return;
var data = JSON.parse(el.getAttribute('data-analytics') || '{}');
if (!window.s || !data.product) return;
s.linkTrackVars = 'events,contextData.product.id,contextData.product.name';
s.linkTrackEvents = 'event12';
s.events = 'event12';
s.contextData['product.id'] = data.product.id;
s.contextData['product.name'] = data.product.name;
s.tl(el, 'o', 'product click');
});
</script>
Expiration, carryover, and single page trips
eVars are sticky by design. That is great for things like visitor type or campaign, and not so great for one time values. The clean way is to pick the right expiration and allocation in the Adobe Analytics admin and keep the code simple. For example, campaign code can expire on visit. A search term on page can expire on hit. Product affinity might use a list var.
For apps that swap content without a full reload, call out page views with s.t, and clear old values before setting new ones. You do not want yesterday’s section sitting in today’s call.
// Before each virtual page view
if (window.s && s.clearVars) { s.clearVars(); }
// set fresh contextData from digitalData
s.contextData['page.name'] = window.digitalData.page.pageName;
s.contextData['page.section'] = window.digitalData.page.section;
s.t(); // virtual page view
When you fire link calls, remember to set s.linkTrackVars and s.linkTrackEvents so your eVars and events travel together. If you forget, the report will look empty and you will spend hours staring at debugger output.
There are also a few tradeoffs. Processing Rules keep code clean, but they need discipline in admin. DTM keeps logic in one place, but you still want a source of truth in the page. Direct eVar calls in code can feel faster for a small site, but the cleanup later is rough when naming or numbers change. For most AEM teams, friendly keys in context data plus a small, shared digitalData object strikes the right balance.
My rule of thumb is simple. Names in AEM, numbers in Analytics. Push rich keys, map them in Processing Rules, and keep DTM focused on reading the page. When a report request lands at 5pm on a Friday, you will be glad you did not hard code eVar12 in four different components.