AEM Sites Developer Expert common mistakes
AEM Sites Developer Expert · Understand the traps and how to avoid them.
Using @Model(adaptables = Resource.class) or SlingHttpServletRequest.class interchangeably without accounting for the fact that request-adaptable models cannot be adapted from a Resource and vice versa.
Candidates memorize the annotation syntax but not the adaptation contract, so they assume any adaptable works for any injection and miss questions about which adapter factory will resolve.
What to do instead
Learn the two adaptables explicitly: Resource-adaptable models are for component/resource data and are the default; request-adaptable models are needed only when you require request attributes, selectors, or bindings. When a model must serve both, register two adaptables or use a delegation pattern, and verify with the Sling Model console that the model resolves.
Writing HTL that calls business logic or performs null-unsafe property access, e.g. ${model.title.toUpperCase()} without a null guard or using scriptlets-style Java in the template.
Candidates coming from JSP or front-end frameworks try to keep logic in the view, and HTL's expression language silently returns empty for nulls only in some cases, so they misjudge what fails.
What to do instead
Keep HTL declarative: expose computed or formatted values from the Sling Model with @Getter methods, use data-sly-test for conditional blocks, and rely on the default null handling (empty output) rather than chaining method calls on possibly-null properties. Prefer ${model.title} over inline transformations.
Confusing editable template structure with policy configuration, e.g. expecting a component's design dialog settings to live in the template's structure node rather than in the template's policies.
The template editor UI groups structure, initial content, and policies visually, so candidates assume all configuration is stored together and answer questions about where a setting is persisted incorrectly.
What to do instead
Memorize the storage model: structure and initial content live under the template's jcr:content/structure and initial nodes; policies live under conf//settings/wcm/policies and are referenced from the template's policies node. Design dialog values come from policies, not from the component node in the template.
Assuming a component dialog field appears to authors simply because it was added to the dialog, without checking whether the dialog is a cq:dialog vs design dialog and whether the field is in the correct tab/container.
Candidates build the dialog XML correctly in isolation but forget that granite/ui/components/foundation containers require proper sling:resourceType and that fields outside a container are not rendered.
What to do instead
Always wrap fields in a granite/ui/components/coral/foundation container (e.g. fixedcolumns or tabs) with correct fieldLabel and name, verify the dialog's sling:resourceType is cq/gui/components/authoring/dialog, and confirm rendering in the component's Edit dialog rather than only validating XML.
Creating a client library with categories that are never included on the page, then wondering why CSS/JS does not load in publish.
Candidates focus on the clientlib folder structure and forget that inclusion is driven by template/page policy categories (e.g. via the Page component's customheaderlibs or the template's policy), and that the category name must match exactly.
What to do instead
Trace the inclusion chain: clientlib categories declared in .content.xml, then referenced by the page/template (commonly through the page component's HTML or the editable template policy). Verify with the page's HTML source and the /libs/granite/ui components, and check that allowProxy and dependencies are set for proxy-served clientlibs.
Configuring a dispatcher farm rule that allows a path but forgetting the corresponding filter rule, so requests are blocked at the filter before reaching cache logic.
Dispatcher has two independent layers (filter and cache/farm rules) and candidates conflate them, assuming a cache rule alone makes a URL reachable.
What to do instead
Treat filter and farm as separate concerns: /filter allows or denies request patterns by method and path, /cache controls caching and invalidation. When a URL 404s or 403s unexpectedly, check /filter first, then /cache rules, and test with dispatcher flush and the dispatcher log at debug level.
Relying on default invalidation and assuming activating a page clears every cached variant, including selector- or query-string-based URLs.
Candidates learn that activation triggers dispatcher flush but not that flush agents match by path and that statfileslevel and ignoreUrlParams govern what is actually invalidated.
What to do instead
Know that activation triggers the flush agent which invalidates by path and its statfile level; configure statfileslevel to control granularity, use ignoreUrlParams for tracking parameters, and explicitly flush selector-based or vanity URLs when needed. Verify with the flush agent logs and by requesting the URL after activation.
Assuming replication of a page also activates referenced content such as experience fragments, assets, or included components from other paths.
Candidates think of replication as a dependency-aware deployment, but the default replication agent only sends the requested path; references are not followed automatically.
What to do instead
Use the Manage Publication / tree activation options or the Activate Tree workflow to include referenced content, keep referenced assets in the same tree where possible, and verify on publish that referenced paths resolve rather than 404.
Building a language copy by translating in place or by copying pages without configuring the language structure and translation cloud service, resulting in pages that are not recognized as language copies.
Candidates treat localization as duplicating content, not as a language-copy relationship, so they miss questions about live copies, translation projects, and the language navigation component.
What to do instead
Create language copies via the References/Language Copy flow so the jcr:language and language-copy relationship are recorded, use translation projects with a configured translation connector, and confirm the language navigation component resolves the structure. Remember that language copies are not live copies unless explicitly created as such.
Granting permissions by adding users to the administrators group or applying ACLs at the wrong node, then expecting least-privilege behavior in author and publish.
Candidates know groups exist but not that permissions are inherited down the tree and that deny ACEs and the order of evaluation determine effective access, so they misjudge effective permissions.
What to do instead
Apply ACLs at the highest node that should carry the permission, prefer groups over users, and use the User/Group admin and the permission checker (or the Access tab on a page) to verify effective rights. Remember deny overrides allow and that publish users are typically anonymous unless configured otherwise.