3.3 Client-Side Libraries (Clientlibs) Architecture & Management
Key Takeaways
- The allowProxy="{Boolean}true" property allows clientlibs stored under /apps to be securely exposed through /etc.clientlibs/, satisfying modern Dispatcher security filters.
- The dependencies property loads prerequisite clientlibs in separate preceding script/style tags, whereas embed inlines and concatenates the files directly into the single bundle.
- Manifest files js.txt and css.txt define file concatenation order and support the #base=<subfolder> directive for relative path resolution.
- Modern front-end tooling (e.g., Webpack in ui.frontend) bundles production assets, allowing AEM clientlibs to serve them with processors set to default:none to avoid double minification.
- Clientlibs are included in HTL templates via /libs/granite/sightly/templates/clientlib.html using clientlib.css, clientlib.js, or clientlib.all.
3.3 Client-Side Libraries (Clientlibs) Architecture & Management
Exam Focus: Client-Side Libraries (
cq:ClientLibraryFolder) manage JavaScript, CSS, fonts, and static assets in AEM Sites. Key AD0-E128 exam topics include: node structure and mandatory properties, the security proxy mechanism (allowProxy="{Boolean}true"exposing/etc.clientlibs/), manifest format (js.txtandcss.txtwith#base), the critical behavioral difference betweendependenciesandembed, clientlib processors and integration with the modernui.frontendmodule, HTL clientlib inclusion templates (clientlib.html), and debugging techniques withdumplibs.
Anatomy of a Client-Side Library (cq:ClientLibraryFolder)
In enterprise web applications, managing dozens of disparate CSS stylesheets and JavaScript files introduces severe performance bottlenecks: excessive HTTP requests, render-blocking scripts, uncoordinated load orders, and cache invalidation failures. AEM solves this challenge through Client-Side Libraries (Clientlibs).
A client library is represented in the JCR as a node of type cq:ClientLibraryFolder. The HTML Library Manager can concatenate and process CSS and JavaScript and expose the result through clientlib URLs; long-cache URL rewriting is a separate configurable delivery concern.
Node Structure in .content.xml
A production client library node located under /apps/myproject/clientlibs/clientlib-site/.content.xml is defined as follows:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0"
jcr:primaryType="cq:ClientLibraryFolder"
categories="[myproject.site]"
dependencies="[core.wcm.components.commons]"
embed="[myproject.tokens,myproject.grid]"
allowProxy="{Boolean}true"/>
Key Clientlib Properties Explained
categories(String[]): The logical identifier(s) used to reference and load the library in HTL templates or page components. Multiple clientlibs can share the same category, in which case AEM merges them.dependencies(String[]): Identifies external clientlib categories that must be loaded before this clientlib. AEM generates separate preceding HTML<link>or<script>tags for each dependency.embed(String[]): Merges the physical source code of the referenced clientlib categories directly into this clientlib's bundle. No separate HTML tag is generated for embedded libraries.allowProxy(Boolean): Mandates whether the clientlib located under/appsor/libscan be securely accessed by web browsers via the public/etc.clientlibs/proxy servlet path.
The Security Proxy Mechanism (allowProxy="{Boolean}true")
In standard enterprise AEM architectures and AEM as a Cloud Service, the Apache Dispatcher and CDN tiers enforce strict security rules that block public HTTP requests to internal repository trees:
# Dispatcher Filter Rule (Security Baseline)
/filter {
/0001 { /type "deny" /url "/apps/*" }
/0002 { /type "deny" /url "/libs/*" }
/0003 { /type "allow" /url "/etc.clientlibs/*" }
}
If a clientlib folder resides in /apps/myproject/clientlibs/site and allowProxy is omitted or set to false, browsers attempting to load /apps/myproject/clientlibs/site.css receive a 403 Forbidden or 404 Not Found error from the Dispatcher.
When allowProxy="{Boolean}true" is set:
- AEM's Granite HTML Library Manager proxies the resource via the
/etc.clientlibs/endpoint. - The request URL becomes
/etc.clientlibs/myproject/clientlibs/site.min.css(or.js). - The Dispatcher permits the request and caches the file on disk.
- When long-cache clientlib URL rewriting is configured, a versioned URL can be used with long browser and CDN cache lifetimes.
allowProxyby itself does not guarantee a hash or a particular Cache-Control header.
Exam Rule: In AEM as a Cloud Service, all clientlibs located under
/appsmust haveallowProxy="{Boolean}true".
Manifest Files: css.txt and js.txt
A cq:ClientLibraryFolder must contain manifest files that declare the exact files to concatenate and their order of execution:
css.txtfor stylesheetsjs.txtfor JavaScript files
Manifest Syntax and the #base= Directive
Manifest files follow strict syntax conventions:
- Lines starting with
#are treated as comments, except for the#base=directive. #base=<subfolder>sets the relative base folder path for all file references that follow it until another#base=directive appears.- Files are concatenated in the exact sequential order listed in the manifest.
Example: css.txt
#base=css
reset.css
typography.css
grid.css
components/header.css
components/footer.css
Example: js.txt
#base=js/vendor
jquery.debounce.js
#base=js
utils.js
navigation.js
main.js
If a file listed in css.txt or js.txt is missing from the repository, AEM logs a warning and skips the file during concatenation. If css.txt or js.txt is missing entirely, AEM will not serve that respective media type from the client library.
dependencies vs embed: Architectural Tradeoffs
The distinction between dependencies and embed is one of the most frequently tested concepts on the AD0-E128 exam.
| Architectural Attribute | dependencies Property | embed Property |
|---|---|---|
| Physical Bundling | Files remain separate; code is not merged. | Files are physically concatenated and inlined into the host bundle. |
| HTML Tag Generation | Generates separate preceding <link> or <script> tags for each dependency. | Generates zero additional HTML tags; code is included within the parent tag. |
| Browser Caching | Dependencies are cached independently by the browser and CDN. | Embedded code is cached only as part of the host bundle. |
| Ideal Use Case | Large, shared libraries (e.g., jQuery, React, Core Components commons). | Small utility CSS, design tokens, private sub-libraries, or isolated polyfills. |
| Common Risk | Additional HTTP requests if HTTP/2 is not optimized. | Duplicate downloads if multiple libraries embed the same shared dependency. |
The Duplicate Download Antipattern
Consider this common architectural antipattern:
- Clientlib
myproject.carouselsetsembed="[vendor.lodash]". - Clientlib
myproject.searchalso setsembed="[vendor.lodash]".
If a page uses both the Carousel and Search components, the end-user's browser downloads the entire lodash library twice in separate bundles. This wastes bandwidth, degrades performance scores, and can cause runtime JavaScript collisions.
Correct Architectural Approach: Declare vendor.lodash in dependencies="[vendor.lodash]" on both components. AEM's HTML Library Manager ensures vendor.lodash is emitted exactly once in the page <head> before either component script executes.
Clientlib Processors & Front-End Build Integration
AEM includes built-in processors for JavaScript and CSS:
- CSS Processors: CSSO, YUI Compressor.
- JS Processors: Google Closure Compiler (GCC), YUI Compressor.
Global processor settings are configured via OSGi in com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl.cfg.json.
Integration with the Modern ui.frontend Module
In modern AEM projects generated by the AEM Project Archetype, all front-end compilation (TypeScript, SCSS, PostCSS, Babel, ESBuild, Webpack, or Vite) takes place inside the ui.frontend Maven module during the build lifecycle.
The build tool compiles, bundles, tree-shakes, and minifies the code, writing the production-ready .js and .css files directly into /ui.apps/.../clientlib-site/.
Because the assets are already minified, running AEM's internal YUI or GCC processors is redundant and dangerous (YUI compressor can fail or break modern ES6+ JavaScript syntax). To disable AEM's internal minification, developers set processor properties on the clientlib node:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0"
jcr:primaryType="cq:ClientLibraryFolder"
categories="[myproject.site]"
allowProxy="{Boolean}true"
cssProcessor="[default:none,min:none]"
jsProcessor="[default:none,min:none]"/>
Setting default:none,min:none instructs AEM to concatenate files without altering or re-minifying the pre-optimized code.
Including Clientlibs in HTL
AEM provides a standard HTL template for including client libraries: /libs/granite/sightly/templates/clientlib.html.
Step 1: Import the Clientlib Template
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html" />
Step 2: Call the Template by Category
Developers can load CSS only, JS only, or both:
<!-- Load only CSS in the <head> -->
<sly data-sly-call="${clientlib.css @ categories='myproject.site'}" />
<!-- Load only JS before the closing </body> tag -->
<sly data-sly-call="${clientlib.js @ categories='myproject.site'}" />
<!-- Load both CSS and JS simultaneously -->
<sly data-sly-call="${clientlib.all @ categories='myproject.site'}" />
Deferred Script Loading
The legacy /libs/granite/sightly/templates/clientlib.html helper does not gain a loading='defer' contract merely from an arbitrary option. Core Components provide a ClientLibraries Sling Model that supports a documented defer=true option. Use that model where available, or render a controlled script element with the appropriate attribute in the page component rather than assuming every clientlib helper understands loading.
Component-Specific vs Site-Wide Clientlibs & Debugging
Modern enterprise architecture divides clientlibs into two tiers:
- Site Clientlibs (
myproject.site): Global reset, typography, responsive grid, header, and footer styles loaded on every page template. - Component Clientlibs (
myproject.components.teaser): Styles and behaviors specific to individual components. In modern AEM, components include their own clientlib only when authored on the page, keeping the base page payload lean.
Debugging Clientlibs with Dumplibs
When diagnosing clientlib issues in local development:
- Granite Dumplibs Tool: Navigate to
/libs/granite/ui/content/dumplibs.htmlto inspect all registered categories, dependency trees, and resolved paths. - Disabling Minification via URL: Append
?debugClientLibs=trueto the page URL. AEM outputs each individual unminified JS and CSS file with line numbers rather than a single concatenated bundle, making browser breakpoint debugging trivial.
What is the primary architectural purpose of setting the property allowProxy="{Boolean}true" on a cq:ClientLibraryFolder node located under /apps?
In AEM client library management, what is the critical behavioral difference between configuring a clientlib category in dependencies versus embed?
Which HTL code snippet correctly imports the standard Granite clientlib template and emits both the stylesheet and JavaScript markup for the category wknd.site?
A project uses an external front-end build tool (such as Webpack in ui.frontend) that already lints, compiles, tree-shakes, and minifies JavaScript and CSS bundles before copying them into /apps/myproject/clientlibs. How should the clientlib folder be configured to prevent AEM from re-minifying the files with its default YUI/GCC processor and risking compilation errors?