Audience : AEM Architects, Leads and Developers
This blog highlights and addresses commonly faced issues while building scalable, dynamic enterprise applications using popular front-end frameworks with AEM (Adobe Experience Manager). Even though there are some tools available to address issues faced with Single Page Applications (SPA), when it comes to traditional (Non-SPA) applications, there is dearth of documentation on most of the issues faced while working with React and Angular.
As front-end frameworks like React, Angular and Vue.js are gaining popularity, understanding the nuances of integration with headful AEM becomes imperative to the success of implementation of a content-centric enterprise application. Even though there are some tools and documentations around development of Single Page Applications using AEM and React or Angular, there is lack of information on implementing a traditional (Non-SPA) application using AEM and modern web frameworks. A typical end-user interfacing portal has following key characteristics:
1. Content-driven: Requires frequent content updates, hence business/ marketing team needs to have full control on the application.
2. Dynamic: Most of the pages have some sort of dynamic functionality which either takes input from user and makes API calls to fetch some data from third or first party.
3. SEO: Most of the pages have SEO value and drives traffic directly from search engines through deep linking.
4. Authentication: Some pages of the application require the user to be authenticated.
Either headful or headless approaches currently followed to implement such applications come with their own disadvantages:
1. With headless approach, SEO is difficult to achieve as the rendering happens on client-side using SPA’s.
2. With headful approach, it is difficult to achieve dynamic functionalities.
3. If implemented in mixed mode (Headless + headful) applications suffer with performance, maintenance issues.
Since there is a good mixture of static and dynamic behavior on the application, the existing approaches fall short of providing a scalable, maintainable, discoverable platform on which the application can be developed.
A hybrid approach which is a combination of headless and headful approaches addresses the above-mentioned disadvantages whilst catering to all requirement mentioned above. In this approach, content-heavy parts of page are rendered in headful mode (server-side rendering) and the dynamic parts of a page are rendered using client-side rendering. Client-side rendering can be done using a modern web framework like React, Angular or Vue.js
Need for MPA against SPA
Here are some of reason why one would stick to multi-page application even though an AEM + SPA combination would solve at least some of the problems discussed here.
1. Retain SEO Value of the web property
2. Simplicity of development
4. SSR in AEM + SPA is complicated and not recommended by AEM vendors.
5. Only a portion of the page needs to be rendered using front-end library as against to a whole page.
In the following sections, key tenets of this approach are discussed in detail.
Component Boundary Definition
In a typical AEM project, components are defined based on reusability as a north star. This still plays an important role in defining component boundaries. But along with reusability, we need to look at integration feasibility with front-end libraries. Here are some more considerations which play an equally important role.
1. Nesting: multiple levels of nesting should be avoided as it would be difficult to implement in front-end. Nesting is seemingly taken care effortlessly in native AEM component, but this would be challenging in its front-end counterpart as it would mean extra effort to implement propagation of dialog variables from parent to child components.
2. Granularity v/s Reusability: More granular the components are designed; more reusable they will be. But more granularity would result in nesting which should be avoided. Hence one would end up in a situation where the reusability of the components suffers.
3. Dynamic Variations: While defining a component, all dynamic variations should be considered. For example: pop-up modals that component produces on interaction. When the content of the component changes based on user-interaction or API response, all possible variation of that component needs to be taken into consideration while defining the component boundary.
Consider a simple login form. This form can be broken down in many ways. Hypothetically, let’s assume we are breaking it down to most granular form for better re-usability. If this was an AEM Native component, this would be practical. But, considering authoring dialogs and complexities of reading those values in front-end component, we would be better off considering this as a monolith (a single login component). This only makes sense because the front-end logic written to make this component work would work only if all elements are present.
Project structure for clear segregation
As the project grows big, it becomes a maintenance nightmare if the native AEM and front-end components are not segregated right from the design phase. Components as well as client-side libraries can be segregated for ease of maintenance.
Approach decision — Native v/s Front-end
While designing a front-end integrated AEM application, a very frequent decision an architect must make is to decide whether to implement a component in AEM Native way or front-end way. Here are some thumb rules which would help in making this decision.
A note on headless content
Providing complex authoring functionality for multiple conditions in a single component dialog might make authoring experience difficult. To make it easier for business authors to author content, content fragments can be employed in case of approach 3.
AEM and Front-end Handshake
A frontend component needs an anchor DOM node to bootstrap itself. Once this anchor DOM node is available, front-end code paints the content within the node. In an SPA, there is usually a root node which provides this capability. But in our AEM application we have multiple components which is required to be rendered in this way. And those components may be sandwiched between native AEM components. Hence, we would need multiple anchor nodes (one for each component) and multiple entry-points to render. With this approach, it is possible to depend on front-end rendering only for dynamic components.
Propagating content from AEM to front-end
A native AEM component provides both authoring and rendering capabilities. With the frontend component in picture, the rendering responsibility will be on the front-end component while the AEM still provides the authoring. Hence, there is a need for a mechanism at the platform level to pass this content to front-end. This can be done using HTML data attributes. The content authored on the AEM component can be packaged to a JSON and rendered on to the anchor node using data attribute. From here the front-end component can be initialized with props injection.
Frontend (React example)
Most of modern web frameworks needs to be transpiled before it can be referenced from HTML. Transpiling is done by using some post-processor like web-pack, babel or some transpiling utility. This mandates to have a separate frontend project and a build process. Each frontend component can be converted to an independent client-lib and can be referenced in the AEM component.
Source Map generation
Since the complete application is rendered from AEM, for debugging of the front-end code, we will have to rely on AEM’s client-libraries. For debugging to work with AEM client-libs, the source maps need to be in an appropriate path. SourceMapDevToolPlugin can be used to customize source map path so that all source maps are present in resource folder.
Handling Performance issues
Improving page load speeds by optimizing CSS and JS inclusions
Loading of multiple CSS and JS gives rise to page load issues as multiple calls lead to render-blocking. Its always a best practice to load all CSS in head and load all JS at the bottom of body tag. But, in AEM since we are loading client-libs in components, there is no control on where the scripts are loaded. This issue can be overcome by a response filter which re-arranges CSS and JS loads using HTML parser like JSoup.
*Scores calculated with conditions: AEM Publish 6.5.7, Dispatcher 4.1.5, Front-end prod build, Apache HTTPD 2.2, with pre-caching, HTTP 1.1 and Windows 10
Loading web assets
Care should be taken that heavier global web assets like fonts, global styles, libraries like bootstrap, sprites are loaded through AEM native client-lib and not from front-end. This results in faster loading of pages and eliminates delays in loading of such heavier assets.
Different load times on native and front-end components
Since native components are pure HTML, they are rendered by browser quicker than front-end components. This delay is unavoidable and results in bad user experience. For example, footer component, which is implemented in native way renders before the body components which are rendered from frontend frameworks and as the page loads, footer gets pushed down. This can be avoided by specifying height to the anchor div element which contains the frontend component.
Team and work break-down
Since there will be multiple handshakes happening between AEM, Front-end and UI developers, it makes sense to have a team consisting of one or more of this combination depending on the quantity of the work. Here is some typical work distribution based on the skillset.
With the above-mentioned guidelines, it is possible to standardize and implement a seamless integration between AEM and modern web framework. This enables us to tap the capabilities provided by modern web frameworks in a content-centric, dynamic web application providing granular control on the application to business users.