AEM Insider
EP08 // AEM Complete Tutorial Series 2026

Clientlibs —
How CSS & JavaScript Reach a Page

Categories · the head & footer · proxy · dependencies · embed
one link, loaded once/etc.clientlibsthat long hash
aeminsider.com · @aeminsider · Not affiliated with Adobe Inc.
// Chapter 01 · The styles have been faking it

We have been cheating.

EP04 / EP05

When a component needed styling, we dropped a <style> block — and sometimes a <script> — right inside the component's HTML. It worked. It's also the wrong way to ship it.

EP07

We opened the page component: two files, customheaderlibs.html and customfooterlibs.html. I said clientlibs would one day plug in there.

Today those two things meet — styles and script out of the component, onto the page the right way.
// Chapter 02 · The quick way, and why it doesn't last

A <style> and a <script>, right in the component.

callout.html
<style>
  .cmp-callout { background:#0c0d10; border:1px solid #d32f2f; … }
  .cmp-callout__button { background:#d32f2f; color:#fff; … }
</style>

<div class="cmp-callout"> … Helpful <span>0</span> </div>

<script> // a click counter, right here in the component </script>
CSS in the body

Not in <head> — the page can paint before styles arrive.

JS mid-page

Not before </body> — can block rendering of everything below.

Tangled together

CSS, JS and markup all in one file — nothing reusable.

Fine for a demo. Wrong for a site — CSS in the body means a flash before styles land.
// Chapter 03 · What a clientlib actually is

A folder AEM knows how to serve.

CRXDE Lite · /apps/aeminsider/clientlibs
clientlib-callout          cq:ClientLibraryFolder
├── css
│   └── callout.css
├── js
│   └── callout.js
├── css.txt
└── js.txt

// folder property   categories = [aeminsider.callout]
categories

The name you reference it by. You never link the .css by path.

css.txt / js.txt

Which files go in, and in what order.

the call

${clientlib.css @ categories='…'} — ask by category, AEM builds the URL.

Take the <style> and the <script> out of the component, and drop them in here.
// Chapter 04 · Add it twice

One file. Wrong place.

Heads up
Instance one
Helpful1✓
Heads up
Instance two
Helpful0
AEM de-duplicated — one script, not two
view-source
<head> … </head>
<body>
  … first Callout …
  <link href="/apps/…/callout.css">
  <script src="/apps/…/callout.js">
  … second Callout … // no duplicate
</body>
↑ both mid-body — not in <head>, not before </body>
AEM de-duplicates — one of each, even with two Callouts. But CSS belongs in the head. JS belongs before the body closes.
// Chapter 05 · Load it once, in the right place

One page. One place. Once.

customheaderlibs.html → <head>
${clientlib.css @ categories='aeminsider.callout'}
customfooterlibs.html → before </body>
${clientlib.js @ categories='aeminsider.callout', async=true}
// Chapter 06 · The proxy path

Your CSS is under /apps. The browser can't see /apps.

no proxy
/apps/aeminsider/components/callout/clientlib.css

Works for admin on the author. On the publish server, /apps is restricted — the link breaks, the page loads unstyled.

allowProxy = true
/etc.clientlibs/aeminsider/components/callout/clientlib.min.<hash>.css

A public copy of the compiled file. Anyone can open it. The source stays private under /apps.

The <hash> is a version stamp built from the file's contents — change the CSS, the hash changes, browsers refetch. Otherwise it caches forever.
Same styles, safe public address. One property does it: allowProxy.
// Chapter 07 · Dependencies and embed

Two ways to pull in another clientlib.

clientlib cq:ClientLibraryFolder
categories = [aeminsider.callout]
dependencies = [aeminsider.callout.theme]
Network
200clientlib-callout-theme.css1st
200clientlib.css2nd
The dependency loads first, then ours. Two calls.
clientlib cq:ClientLibraryFolder
categories = [aeminsider.callout]
embed = [aeminsider.callout.theme]
Network
200clientlib.csstheme on top, callout after
The theme is combined into our file. One call.
Depend → shared libs like jQuery, cached once. Embed → bundle many into one file (our base clientlib embeds the grid + core components).
// Chapter 08 · Chaining clientlibs

One chains all the way. One doesn't.

dependencies · transitive
A→B→C depends on

Load A and AEM follows the whole chain. C, B and A — all three end up on the page.

embed · NOT transitive
A→B→C embeds

Load A and you get B + A only, combined into one file. C is left out.

Depend, and the whole chain comes along. Embed pulls in only what you list directly.
// Chapter 09 · How CSS and JS reach the page

The whole path, in order.

1Write CSS/JS in a clientlib folder and give it a category.
2The page component asks for it by category — CSS in customheaderlibs, JS in customfooterlibs.
3allowProxy serves the compiled file from /etc.clientlibs.
4The hash versions it — so it caches forever, and updates the moment you edit.
5dependencies and embed pull in whatever else it needs.
// Next on AEM Insider

Next: HTL

Every component file we've written ends in .html — but it isn't HTML. It's HTL (a.k.a. Sightly), AEM's template language: data-sly-use, data-sly-list, data-sly-test, the ${…} expressions and their contexts — the language behind everything we've built.

EP09 · the language we've used all along