A detailed explanation of the clientlib.config.cjs file variables and what the configuration compiles to in an AEM project.
In this configuration how-to, I will explain clientlib.config.cjs file variables and what the configuration compiles to in an AEM (Adobe Experience Manager) project.
This is a companion guide to HMR & Y. O. U., which covers Hot Module Reloading and the basics of setting up client libraries.
If you've ever worked on the front end of an AEM project, you're probably pretty familiar with clientlibs. But have you ever wondered how your compiled JavaScript and CSS actually end up in AEM? We write our TypeScript, Sass, or whatever else we're using, Webpack compiles everything, and somehow it all ends up neatly organized under /apps. Well, there's a little more going on behind the scenes.
In this article, we're going to look at clientlib.config.js, which is part of a custom front-end build configuration using aem-clientlib-generator.
This isn't an AEM configuration file itself. It's a Node.js configuration that tells the generator where to find our compiled assets, how to organize them into client libraries, and where to put the generated files.
Let's break down what each part does and how everything eventually makes its way into AEM.
Before we start generating anything, we need to tell our build process where to find the compiled front-end files and where to put the generated clientlibs.
We have two important paths:
BUILD_DIR — The directory containing our compiled assets. In this example, Webpack outputs everything into ui.frontend/dist.
CLIENTLIB_DIR — The location in our Maven project's source tree where aem-clientlib-generator writes the clientlibs. Here, we're using packages/application/src/main/content/jcr_root/apps/myapp/clientlibs.
CLIENTLIB_DIR isn't pointing directly at AEM's JCR repository.
We're still working with files in our local project. Those files eventually get packaged and deployed to the corresponding repository path in AEM. Depending on your project structure, you might see generated clientlibs under ui.apps instead. Both approaches follow the same general idea.
libsBaseConfig)| Property | Value | Purpose |
|---|---|---|
| allowProxy | true | Allows AEM to proxy client library requests |
| serializationFormat | 'xml' | Uses XML format for AEM client library metadata |
| cssProcessor | ['default:none', 'min:none'] | No CSS processing/minification |
| jsProcessor | ['default:none', 'min:none'] | No JS processing/minification |
You might have noticed that both cssProcessor and jsProcessor are set to none.
That's intentional. We're already using Webpack to compile and optimize our front-end assets, so we don't necessarily want AEM running another minification pass on the generated files.
Depending on your build configuration, running multiple processors can be unnecessary and can occasionally introduce issues. By disabling AEM's additional processing here, we're leaving that responsibility to our front-end build. Just remember that this assumes your build is already handling optimization appropriately. If it isn't, disabling minification in AEM could mean you're serving larger files than necessary.
| Property | Value | Purpose |
|---|---|---|
| Name | clientlib-dependencies | Client library identifier |
| Categories | ['myapp.dependencies'] | AEM category for including this library |
| Dependencies | None | No dependencies on other client libraries |
| Embed | None | No embedded libraries |
| Assets - JS | cwd: 'clientlib-dependencies'files: ['*/.js']flatten: false | Copies all JS files from dependencies folder |
| Assets - CSS | cwd: 'clientlib-dependencies'files: ['*/.css']flatten: false | Copies all CSS files from dependencies folder |
Now, these two properties might look pretty similar, but they don't do the same thing.
dependencies tells AEM that another clientlib needs to be included when this one is requested.
For example, if our clientlib-site depends on myapp.dependencies, AEM includes that dependency before our site library. embed, on the other hand, combines the referenced clientlib's contents into the generated output of the embedding clientlib.
So why would we use one over the other?
It depends on how we want those assets delivered. Sometimes we want separate clientlibs, and sometimes we want their contents combined. One thing to keep in mind, especially with AEM as a Cloud Service, is that modern bundlers like Webpack can already handle a lot of our dependency management and asset bundling.
Adobe's current Cloud Service guidance actually discourages relying on clientlib dependencies and embed when they aren't needed. That doesn't mean you'll never see them in an existing project. You absolutely will. But it's worth understanding how they work before deciding whether they're necessary.
| Property | Value | Purpose |
|---|---|---|
| Name | clientlib-site | Client library identifier |
| Categories | ['myapp.site'] | AEM category for including this library |
| Dependencies | ['myapp.dependencies'] | Depends on dependencies library |
| Embed | ['myapp.grid'] | Embeds grid system library |
| Assets - JS | cwd: 'clientlib-site'files: ['*/.js']flatten: false | Copies all JS files from site folder |
| Assets - CSS | cwd: 'clientlib-site'files: ['*/.css']flatten: false | Copies all CSS files from site folder |
| Assets - Resources | cwd: 'clientlib-site'files: ['*/.']flatten: falseignore: ['/.js', '*/.css'] | Copies all other files (fonts, images) excluding JS/CSS |
Preparing diagram…
/apps/myapp/clientlibs/
├── clientlib-dependencies/
│ ├── css.txt # Lists CSS files for AEM
│ ├── js.txt # Lists JS files for AEM
│ ├── css/
│ │ └── dependencies.css
│ └── js/
│ └── dependencies.js
└── clientlib-site/
├── css.txt # Lists CSS files for AEM
├── js.txt # Lists JS files for AEM
├── css/
│ └── site.css
├── js/
│ └── site.js
└── resources/ # Fonts, images, etc.
├── fonts/
│ ├── google/
│ └── font-awesome/
└── images/
├── png/
└── svg/
| Context | Code Example | Purpose |
|---|---|---|
| CSS Inclusion | <div data-sly-call="{clientLib.css @ categories='myapp.site'}"></div> | Includes site CSS in page head |
| JS Inclusion | <div data-sly-call="{clientLib.js @ categories='myapp.site'}"></div> | Includes site JavaScript before closing body |
| Dependencies | clientlib-site automatically loads clientlib-dependencies first | Ensures proper loading order |
| Development | Hot Module Reloading available during development | Live updates without page refresh |
| Benefit | Description |
|---|---|
| Dependency Management | Automatic loading order with dependencies and embed properties |
| Caching | AEM handles client library caching and versioning |
| Minification | Optional CSS/JS minification through AEM processors |
| CDN Support | Client libraries can be served from CDN |
| Development Workflow | Hot reloading and live sync during development |
Let’s chat about how this all fits together and why it’s important for a modern, well-architected project.
Once you understand what clientlib.config.cjs is doing, the process becomes pretty straightforward. Webpack handles compiling our front-end code. aem-clientlib-generator takes that output and organizes it into clientlibs. Maven packages those clientlibs, and our deployment process makes them available to AEM.
The next time your JavaScript or CSS isn't showing up where you expect it, you'll have a much better idea of where to look. Start with the compiled assets. Then check the generator's output, the clientlib configuration, and finally what actually made it into AEM. Because sometimes the problem isn't your JavaScript at all.
Sometimes it just never made it into the package. ;)
AEM developer and frontend specialist with expertise in Adobe Experience Manager, React, and modern web development. Passionate about creating accessible, performant web experiences.
Comments
Comments are not yet implemented. This is a placeholder for future comment functionality.