Skip to main content
jrmoya73
Level 1
September 1, 2026
Pergunta

Server-Side Geolocation Personalization in AEM as a Cloud Service Without Sacrificing CDN Caching

Introduction

Personalization and caching can initially appear to be opposing concepts.

Caching works best when many users requesting the same URL can reuse exactly the same response. Personalization, on the other hand, means that the same URL may produce different content depending on the context of the request.

This becomes particularly interesting when the personalization criterion is the visitor's geographical location.

Consider a global website built on Adobe Experience Manager as a Cloud Service where the same page must:

  • display different content depending on the visitor's geographical market;

  • show or hide certain components for specific markets;

  • render the final personalized HTML on the server;

  • remain SEO-friendly;

  • avoid client-side content replacement;

  • and continue benefiting from both Adobe's CDN and Dispatcher caching.

A naive implementation can easily solve the personalization requirement while significantly reducing the cache hit ratio.

This article describes an architectural pattern for implementing server-side geolocation personalization in AEM as a Cloud Service while preserving efficient CDN and Dispatcher caching.

The core idea is:

Personalization does not necessarily require disabling caching. Instead, the personalization dimension should become a small, predictable and controlled part of the cache identity.

1. The challenge: personalization vs caching

Imagine the following public URL:

https://www.example.com/en/home

Visitors from different geographical areas may need slightly different experiences.

For example:

Visitor from Spain

South European market experience


Visitor from Mexico

Latin American market experience


Visitor from the United States

North American market experience

The public URL remains exactly the same.

Only some sections of the page might change:

+------------------------------------------------+
| Header |
+------------------------------------------------+
| Hero |
| |
| South Europe → Mediterranean campaign |
| LATAM → Caribbean campaign |
| North America → Winter escape campaign |
+------------------------------------------------+
| Destination cards |
+------------------------------------------------+
| Global editorial content |
+------------------------------------------------+
| Footer |
+------------------------------------------------+

One possible solution would be to return a generic page and replace parts of the DOM with JavaScript after determining the visitor's location.

While technically possible, that approach introduces several disadvantages:

  • personalized content is not necessarily part of the initial HTML;

  • additional client-side logic or requests may be required;

  • content can visibly change after the page loads;

  • layout shifts may occur;

  • important personalization logic moves from AEM into the browser.

For content-driven websites, server-side rendering can therefore be a much more attractive approach.

But SSR introduces another challenge.

If:

/en/home

can produce three different HTML responses, storing a single cached object for that URL is clearly incorrect.

Conceptually, the cache identity needs to evolve from:

URL

to:

URL + personalization variant

while keeping the public URL unchanged.

2. Geolocation at the Adobe-managed CDN

AEM as a Cloud Service already provides useful geographical information at its Adobe-managed CDN.

The CDN adds request headers such as:

x-aem-client-country
x-aem-client-continent

and the CDN configuration layer exposes request properties including:

clientCountry
clientRegion
clientContinent

The country value is based on an ISO 3166-1 Alpha-2 country code. [1][2]

This means that a geographical personalization architecture does not necessarily need an additional geolocation service just to identify the originating country.

The initial request flow can therefore be represented as:

                        Internet


┌─────────────────┐
│ Adobe-managed │
│ CDN │
│ │
│ clientCountry │
└────────┬────────┘


┌─────────────────┐
│ Apache / │
│ Dispatcher │
└────────┬────────┘


┌─────────────────┐
│ AEM Publish │
│ │
│ Sling Models │
│ OSGi Services │
└─────────────────┘

Knowing the country, however, only solves the first part of the problem.

A more important architectural question is:

Should the country really become the personalization and cache dimension?

Note: This article assumes that the Adobe-managed CDN is the edge receiving the client request. Adobe explicitly notes that when a customer-managed CDN is placed in front of AEM, the AEM geolocation headers may represent the location of that proxy rather than the original visitor. [1]

3. From country to a bounded business variant

Suppose a global website operates in 70 countries.

If every country becomes an independent personalization variant, a single page could theoretically have:

70 variants

For 10,000 personalized pages:

10,000 × 70
=
700,000 potential page/variant combinations

Not every combination will necessarily exist in cache at the same time, but the potential cardinality grows quickly.

More importantly, many businesses do not actually personalize independently for every country.

Several countries often share the same commercial or editorial experience.

For example:

ES ─┐
PT ─┼──► SOUTH_EUROPE
IT ─┘


US ─┐
CA ─┴──► NORTH_AMERICA


MX ─┐
AR ─┼──► LATAM
CL ─┘

Instead of implementing:

country

personalized content

we can introduce a business abstraction:

country

market

personalized content

The number of variants can therefore change from dozens of countries to a much smaller controlled set:

default
south-europe
north-america
latam
asia-pacific

The exact markets are business-specific.

What matters technically is that they form a small, stable and bounded set of values.

For example:

10,000 pages × 5 markets
=
50,000 potential page/variant combinations

compared with:

10,000 pages × 70 countries
=
700,000 potential page/variant combinations

Adobe's current page variant caching documentation explicitly requires the variant dimension to remain bounded. The Adobe-managed CDN currently supports up to 200 unique x-aem-variant values; exceeding that can result in a Too many variants response. Adobe specifically uses user IDs as an example of an inappropriate variant key because of their cardinality. [3]

This leads to one of the most important design questions in the entire architecture:

The most important question is not “How do I detect the visitor's country?” but “What is the smallest stable business dimension that actually changes the rendered experience?”

Very often, that dimension is a market, not an individual country.

4. Resolving country into market at the edge

AEM as a Cloud Service supports CDN request transformations through cdn.yaml.

These transformations can evaluate properties such as clientCountry and can set, unset or transform request headers, query parameters, cookies, paths and variables. [2]

This gives us an opportunity to normalize geographical information before the request reaches Dispatcher.

Conceptually:

clientCountry = ES


country → market


market = south-europe

For caching purposes, we can represent that bounded business dimension using:

x-aem-variant: south-europe

A simplified and anonymized example could look like this:

kind: "CDN"
version: "1"

data:
requestTransformations:
rules:

# Do not trust a value supplied directly by the client.
- name: remove-client-variant
when: "*"
actions:
- type: unset
reqHeader: x-aem-variant

# Establish a predictable fallback.
- name: default-market
when:
reqProperty: tier
equals: publish
actions:
- type: set
reqHeader: x-aem-variant
value: default

- name: south-europe-market
when:
allOf:
- reqProperty: tier
equals: publish
- reqProperty: clientCountry
in:
- ES
- PT
- IT
actions:
- type: set
reqHeader: x-aem-variant
value: south-europe

- name: north-america-market
when:
allOf:
- reqProperty: tier
equals: publish
- reqProperty: clientCountry
in:
- US
- CA
actions:
- type: set
reqHeader: x-aem-variant
value: north-america

- name: latam-market
when:
allOf:
- reqProperty: tier
equals: publish
- reqProperty: clientCountry
in:
- MX
- AR
- CL
actions:
- type: set
reqHeader: x-aem-variant
value: latam

The market names and country groupings above are deliberately fictional.

The important transformation is:

Adobe-managed CDN

clientCountry = ES


business mapping


x-aem-variant = south-europe

The visitor still requests:

/en/home

while the delivery architecture knows that the request belongs to:

south-europe

There is also an important security property here.

The incoming x-aem-variant value is removed before the trusted value is assigned. This prevents an arbitrary browser-supplied value from becoming authoritative personalization context.

How this differs from Adobe's documented page-variant example

Adobe's official page variant tutorial uses a slightly different starting point.

In the documented pattern, application code initially sets an x-aem-variant cookie. On subsequent requests, the Adobe-managed CDN transforms that cookie into an x-aem-variant request header. Dispatcher then translates the header into a Sling selector. [3]

Conceptually, Adobe's example is:

Application

x-aem-variant cookie

Adobe CDN

x-aem-variant header

Dispatcher

variant selector

For a pure geographical use case, however, the Adobe-managed CDN already knows clientCountry.

That allows us to derive a bounded business variant directly at the edge:

clientCountry

country → market

x-aem-variant

Dispatcher

variant selector

This removes the need for an additional application round-trip simply to establish a geographical variant.

The rest of the caching pattern remains aligned with Adobe's documented variant-caching architecture. [3]

5. Caching the variant at CDN and Dispatcher

AEM Publish is protected by two important caching layers:

Browser


Adobe-managed CDN


Dispatcher


AEM Publish

Adobe explicitly describes the managed CDN and Dispatcher as the two primary caching layers protecting AEM Publish. [5]

A correct SSR personalization architecture needs both layers to understand the variant.

CDN cache identity

At the CDN level, the response should identify the request header that changes the representation.

For example:

Vary: x-aem-variant

Conceptually:

/en/home
+
x-aem-variant: south-europe


CDN cache object A


/en/home
+
x-aem-variant: north-america


CDN cache object B

Adobe's page variant documentation specifically requires the Dispatcher response to contain:

Vary: x-aem-variant

so the CDN can maintain different cached responses for the different header values. [3]

When adding the header in Apache, it is generally preferable to preserve other possible Vary values rather than overwrite them:

<LocationMatch "^/content/example/.*\.html$">
Header merge Vary "x-aem-variant"
</LocationMatch>

The exact scope should be adapted to the site.

It is usually a mistake to apply geographical cache variation indiscriminately to every resource.

Dispatcher cache identity

Dispatcher needs a different mechanism.

The x-aem-variant value can be converted into an internal Sling selector.

The browser requests:

/en/home.html

while Apache internally resolves something conceptually similar to:

/content/example/en/home.variant=south-europe.html

For another market:

/content/example/en/home.variant=north-america.html

Dispatcher now sees different cacheable resources.

A simplified rewrite could look like this:

RewriteCond %{REQUEST_URI} ^/en/.*\.html$
RewriteCond %{HTTP:x-aem-variant} ^(default|south-europe|north-america|latam)$
RewriteRule ^([^?]+)\.(html.*)$ \
/content/example$1.variant=%1.$2 [PT,L]

This is intentionally an architectural example rather than a drop-in rule because public URL mappings vary between AEM implementations.

The important transformation is:

Public URL

/en/home.html



Internal AEM / Dispatcher identity

/content/example/en/home.variant=south-europe.html

This pattern is directly aligned with Adobe's variant-caching guidance, which uses:

/page.variant=NY.html

to allow both AEM Publish and Dispatcher to distinguish the variant. [3]

The public URL never changes.

6. End-to-end request flow

Putting everything together:

                          REQUEST


┌─────────────────────┐
│ Adobe-managed CDN │
│ │
│ clientCountry │
│ │ │
│ ▼ │
│ Country → Market │
│ │ │
│ ▼ │
│ x-aem-variant │
└─────────┬───────────┘

CDN CACHE?
/ \
HIT MISS
│ │
▼ ▼
RESPONSE ┌────────────────┐
│ Dispatcher │
│ │
│ Internal │
│ variant=market │
│ selector │
└───────┬────────┘

DISPATCHER CACHE?
/ \
HIT MISS
│ │
▼ ▼
RESPONSE AEM Publish


SSR personalization


HTML

On the first uncached request for a page/market combination:

CDN MISS
+
Dispatcher MISS
=
AEM Publish rendering

After that variant has been generated, later requests can be served from Dispatcher or, ideally, directly from the Adobe-managed CDN.

That leads to an important property of this architecture:

Personalization happens when a page variant needs to be generated. It does not necessarily need to be recalculated for every visitor request.

This is fundamentally different from request-by-request personalization.

7. Keeping personalization out of component code

A Hero component should not need to understand CDN geolocation.

Code like this inside every Sling Model would tightly couple component logic to infrastructure:

String country =
request.getHeader("x-aem-client-country");

if ("ES".equals(country)) {
// render configuration A
}

Instead, introduce an application-level abstraction:

public interface MarketContextService {

String getMarket(
SlingHttpServletRequest request
);

}

Because Dispatcher has already converted the variant into a Sling selector, AEM Publish does not need to depend directly on the custom request header.

It can resolve the business market from:

variant=south-europe

For example:

@Component(service = MarketContextService.class)
public class MarketContextServiceImpl
implements MarketContextService {

private static final String VARIANT_PREFIX = "variant=";
private static final String DEFAULT_MARKET = "default";

private static final Set<String> ALLOWED_MARKETS =
Set.of(
"south-europe",
"north-america",
"latam"
);

@Override
public String getMarket(
SlingHttpServletRequest request) {

for (String selector :
request.getRequestPathInfo().getSelectors()) {

if (!selector.startsWith(VARIANT_PREFIX)) {
continue;
}

String market = selector.substring(
VARIANT_PREFIX.length()
);

if (ALLOWED_MARKETS.contains(market)) {
return market;
}
}

return DEFAULT_MARKET;
}
}

The application therefore has a clean chain of responsibility:

CDN geolocation

country → market

x-aem-variant

Dispatcher selector

MarketContextService

personalized components

Components only understand:

market

They do not need to understand:

clientCountry
CDN transformations
HTTP cache variation
Dispatcher rewrites

Reusable component personalization

Once the market has been abstracted, components can support reusable personalization behavior.

A component could conceptually contain:

Fallback configuration

Market variations
├── south-europe
├── north-america
└── latam

Visibility rules
├── visible everywhere
├── visible only in selected markets
└── hidden in selected markets

Server-side resolution becomes:

Current market


Is component visible?

YES│

Does a market variation exist?

┌──┴───┐
YES NO
│ │
▼ ▼
Market Fallback
config config

This behavior can live in a reusable Sling Model base class, delegation layer or OSGi service rather than being reimplemented by every component.

HTL should ideally only consume the final resolved state:

public boolean isVisible();

public String getTitle();

public String getDescription();

public String getImage();

public String getLink();

By the time HTL runs, the personalization decision has already happened.

The HTML returned by AEM is therefore the final server-rendered experience.

Fallback is mandatory

Geolocation should never make page rendering fragile.

A predictable fallback should exist for:

unknown country
unmapped country
missing market variation
invalid variant

For example:

Unknown country

default market

fallback component configuration

Personalization should enhance the page, not become a new availability dependency.

8. Author-side market emulation

Server-side personalization creates an authoring challenge.

An editor physically located in Europe may need to preview the experience intended for Latin America.

Author therefore needs an emulation mechanism.

A useful abstraction is:

PUBLISH

└──► market resolved from trusted delivery context


AUTHOR

└──► explicitly selected emulated market

For example, Author could use an internal emulation cookie:

x-demo-emulated-market=latam

and expose a toolbar or selector with values such as:

Default
South Europe
North America
LATAM

The same MarketContextService abstraction can support both environments:

Author

emulated market

MarketContextService


Publish

variant selector

MarketContextService

Only the mechanism used to determine the context differs.

The actual personalization engine remains the same.

This matters because personalization is not only a delivery concern. It is also an authoring concern.

Editors should be able to preview every supported experience before publishing content.

9. Avoiding unnecessary cache fragmentation

Once variant caching works, it can be tempting to apply it everywhere.

That should be avoided.

There is usually no reason for all these resources to have market variants:

CSS
JavaScript
fonts
icons
global images
robots.txt
unrelated JSON APIs

if their output is identical for every market.

A better strategy is:

Personalized HTML

VARY


Global CSS / JS / fonts / assets

DO NOT VARY

The same principle applies to pages.

If a page cannot contain personalized output, creating multiple geographical cache representations for it adds no value.

The variant dimension should only be introduced where the response can actually differ.

Why not simply use a query parameter?

Another possible implementation would introduce something like:

/en/home?market=south-europe

internally.

Changing the URL can affect CDN cache identity, and Adobe provides CDN configuration examples where query parameters are introduced specifically to split cache entries. [7]

Dispatcher, however, treats query parameters differently.

Adobe's Dispatcher documentation states that:

  • when all query parameters are configured to be ignored, the page can be cached but subsequent requests reuse the same cached representation regardless of parameter value;

  • when a non-ignored parameter is present, the page is not cached by Dispatcher. [6]

For a bounded personalization dimension where both CDN and Dispatcher must cache separate representations, an internal Sling selector creates a much clearer model:

page.variant=south-europe.html

page.variant=north-america.html

Each variation receives an explicit Dispatcher cache identity.

10. Invalidation, observability and testing

Cache invalidation

Variant caching does not eliminate cache invalidation requirements.

If a personalized component changes, representations may exist at both:

Adobe CDN
Dispatcher

The invalidation strategy must therefore account for the page variants associated with that content.

Country-to-market mappings also deserve special attention.

Changing:

ES → south-europe

to:

ES → another-market

changes which representation should be served for future requests from Spain.

Changes to the geographical mapping and the cache strategy should therefore be considered together.

Observability

Personalization across several caching layers becomes difficult to troubleshoot without observability.

CDN request transformations support custom log properties. [2]

Useful information can include:

clientCountry
resolvedMarket
requestedPath
variant
cache status

For example:

requestTransformations:
rules:
- name: log-geolocation
when: "*"
actions:
- type: set
logProperty: client_country
value:
reqProperty: clientCountry

- type: set
logProperty: selected_variant
value:
reqHeader: x-aem-variant

This makes questions such as these considerably easier to answer:

Which country did the CDN detect?

Which market was selected?

Which variant was requested?

Was the response generated by AEM Publish?

Was it returned by Dispatcher?

Was it returned directly from the CDN?

Testing

A useful test strategy should validate every layer independently.

Geographical mapping

Verify expected mappings:

ES → south-europe
PT → south-europe
US → north-america
MX → latam

Fallback

Verify unknown or unmapped locations:

unknown

default

Server-side rendering

Confirm that the initial HTML already contains the personalized output and does not depend on client-side replacement.

Dispatcher isolation

Verify that different markets generate different internal cached resources:

home.variant=south-europe.html

home.variant=latam.html

CDN caching

Verify that repeated requests for the same page and variant become CDN cache hits.

Cross-market isolation

Most importantly, ensure that a cached response for:

south-europe

can never be returned for:

latam

This is one of the most important functional tests in the implementation.

11. SEO considerations

One advantage of SSR personalization is that the final personalized experience is already present in the initial HTML.

Instead of:

HTML


JavaScript


Geolocation


Fetch personalized content


Replace DOM

the flow becomes:

Request


Server-side market resolution


SSR personalization


Final HTML

However, geographical personalization should still be used carefully.

The same URL should normally preserve the same fundamental intent and semantic meaning across markets.

Changing:

  • a promotional Hero;

  • a CTA;

  • campaign messaging;

  • recommendation ordering;

  • selected cards;

  • component visibility;

is very different from serving completely unrelated content under the same canonical URL.

Personalization should therefore complement a proper multilingual and multi-regional content architecture, not replace it.

12. Key takeaways

Server-side personalization and efficient caching are not mutually exclusive.

They can coexist when personalization is deliberately incorporated into the delivery architecture.

The main principles are:

  1. Derive personalization from trusted edge context.

    The Adobe-managed CDN already exposes geographical information that can be used to determine the request's country.

  2. Normalize country into a bounded business variant.

    Avoid creating a cache dimension that is more granular than the business requirement. A small set of markets is usually more scalable than dozens of independent country variants.

  3. Make both CDN and Dispatcher understand the variant.

    Vary: x-aem-variant separates representations at the CDN, while an internal Sling selector provides an explicit cache identity for Dispatcher.

  4. Keep infrastructure details outside individual components.

    Components should consume a business concept such as market, not understand CDN headers, country mappings or caching rules.

  5. Treat authoring, invalidation and observability as part of the architecture.

    A personalization engine is incomplete if authors cannot preview its variants or developers cannot determine which variant was served and from which cache layer.

  6. Treat cache cardinality as an architectural constraint from the beginning.

    Variant values should always be small, stable, predictable and bounded.

The most important distinction is this:

With the right cache architecture, personalization does not need to be recalculated for every visitor. It only needs to be calculated when a particular page variant is not already available in cache.

That distinction makes server-side geographical personalization practical at scale in Adobe Experience Manager as a Cloud Service.

References

[1] Adobe Experience League — CDN in AEM as a Cloud Service

Official Adobe documentation covering the Adobe-managed CDN, including x-aem-client-country, x-aem-client-continent, ISO country codes, geographical caching considerations and the behavior of geo headers when a customer-managed CDN is placed in front of AEM.

Adobe Experience League — CDN in AEM as a Cloud Service

[2] Adobe Experience League — Configuring Traffic at the CDN

Official documentation for cdn.yaml, including request transformations, clientCountry, clientRegion, clientContinent, condition predicates, request headers, variables and custom CDN log properties.

Adobe Experience League — Configuring Traffic at the CDN

[3] Adobe Experience League — Caching Page Variants with AEM as a Cloud Service

Adobe's implementation guide for caching multiple representations of the same page using x-aem-variant, Vary, Sling selectors and Dispatcher. It also documents the current maximum of 200 unique variant values.

Adobe Experience League — Caching Page Variants with AEM as a Cloud Service

[4] Adobe Experience League — Caching in AEM as a Cloud Service

Official documentation describing caching behavior in AEM as a Cloud Service and HTTP headers such as Cache-Control and Surrogate-Control.

Adobe Experience League — Caching in AEM as a Cloud Service

[5] Adobe Experience League — AEM Publish Service Caching

Adobe's overview of the two primary caching layers protecting AEM Publish: the AEM as a Cloud Service CDN and Dispatcher.

Adobe Experience League — AEM Publish Service Caching

[6] Adobe Experience League — Configure AEM Dispatcher

Official Dispatcher documentation, including ignoreUrlParams behavior and how query parameters affect Dispatcher caching.

Adobe Experience League — Configure AEM Dispatcher

[7] Adobe Experience League — CDN Configuration Snippets for Common Scenarios

Official examples for CDN request transformations and cache-key-related scenarios, including examples where the request URL is modified to create different CDN cache entries.

Adobe Experience League — CDN Configuration Snippets for Common Scenarios

Disclaimer

The architecture, paths, market names, mappings and code snippets in this article are intentionally simplified and fictional.

They are intended to illustrate an architectural pattern rather than provide a drop-in production configuration.

CDN, Dispatcher, caching, invalidation, security and personalization rules should always be adapted and validated for the requirements of each AEM as a Cloud Service implementation.