Real cases · 7 of 25
Where AEM components are actually used in real projects, the genuine problems they cause when built carelessly, and honest criteria for when a component is the right tool versus overkill, with a full working example.
Part of the Real-World AEM Problems series. Background reading: AEM Core Components: Use, Configure, Extend, or Build? covers the component decisions behind what's discussed here.
This is the first in a pass through AEM's core building blocks — component, servlet, OSGi service, workflow, and beyond — each one answering three practical questions: where do you actually use this, what real problems come up once it's in production, and when do you genuinely need it versus when reaching for it is overkill.
A reusable content block authors place and configure themselves. The most common real reason to build a component: an author needs to drop a "featured article" block, a "call to action" banner, or a "product card" into a page, configure a few fields through a dialog, and have it render consistently — without a developer touching code for every instance. If content needs to be authorable and reused across many pages, a component is almost always the right call.
A structural piece of a page template. Headers, footers, breadcrumbs, and navigation are components too, even though authors rarely touch their configuration directly — they exist because the page's structure itself is broken into independently maintainable, independently rendered pieces, which is what makes template changes (redesigning the header once) apply everywhere without touching every page.
A wrapper around a piece of dynamic or external data. The pricing component from the previous post in this series is a component for this exact reason — it needs a place in the page's rendering pipeline to fetch, cache, and display data that isn't static authored content.
Dialog field bloat. A component starts with three configurable fields and organically grows to fifteen over a year of "just add one more option" requests, until authors need a decision tree to fill out a single dialog correctly. This is rarely caught early because each individual addition seems small — it's the accumulated total that becomes the real problem, and by the time it's obviously too much, splitting it back into focused components is a real content migration effort, not a quick refactor.
Resource type resolution surprises. A component inheriting from a superType (via sling:resourceSuperType) behaves correctly until someone overrides one script in the child without realizing the parent's other scripts (clientlibs, dialog structure) don't automatically follow the same override — a genuinely common source of "why does this one field look different only in this one place" bugs that take longer to trace than they should, precisely because the inheritance is implicit rather than visible in the component's own folder.
A component doing too much in HTL. Business logic (conditional rendering rules, data transformation, formatting) creeping directly into HTL markup instead of living in the Sling Model — HTL can technically do a lot with data-sly-test and inline expressions, but a component whose actual logic is scattered across markup rather than concentrated in a testable Java class is hard to debug and effectively untestable without spinning up a full page render.
Build a component when: the content needs to be authored and placed by non-developers, when the same visual/functional pattern repeats across multiple pages or sites, or when a piece of the page needs its own independent rendering logic (like an external data fetch) that shouldn't be tangled into the parent template's code.
Skip it (or keep it much simpler) when: the content is genuinely static and page-specific — a component with a dialog full of fields that will only ever be filled out once, for one page, is solving an authoring-flexibility problem nobody actually has. In that case, a static page-specific include or even hardcoded markup in the template is more honest about the actual requirement, and a lot less to maintain than a "flexible" component that only ever gets used one way.
A genuinely common overkill pattern: building a fully dialog-configurable component for something that's actually a one-time layout decision made by a designer, not an ongoing editorial choice made by content authors. If the "configuration" a component dialog offers isn't something an actual author will meaningfully change page to page, that configurability is complexity paid for with no real return.
A "featured article" component built with exactly the scope the requirement calls for — no more:
@Model(adaptables = Resource.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class FeaturedArticleModel {
@ValueMapValue
private String articlePath;
@ChildResource
private Resource image;
@SlingObject
private ResourceResolver resourceResolver;
private Page article;
@PostConstruct
protected void init() {
if (articlePath != null) {
PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
article = pageManager != null ? pageManager.getPage(articlePath) : null;
}
}
public String getTitle() {
return article != null ? article.getTitle() : "Article unavailable";
}
public String getExcerpt() {
return article != null
? article.getProperties().get("jcr:description", "")
: "";
}
public boolean isAvailable() {
return article != null;
}
}
<div class="featured-article" data-sly-use.model="com.example.core.models.FeaturedArticleModel"
data-sly-test="${model.available}">
<h3>${model.title}</h3>
<p>${model.excerpt}</p>
</div>
<div class="featured-article featured-article--missing" data-sly-test="${!model.available}">
<p>Featured article is no longer available.</p>
</div>
Notice what's deliberately absent: no dialog options for things nobody asked to configure, no business logic embedded in the HTL beyond a straightforward availability check, and the model handles the one real failure case (a referenced article that's been deleted or unpublished) explicitly rather than letting the page break.
Next in this pass through AEM's building blocks: servlets — where they're actually the right tool versus where a Sling Model or component would serve better, and the real problems (selector conflicts, security exposure) that come with getting that choice wrong.
Get an email when I publish the next chapter. No spam — just new technical deep-dives.
Comments
Share feedback or questions about this blog post.
No comments yet. Be the first to share your thoughts.