When deciding what level of integration you would like to have between your external SPA and AEM, you often need to be able to edit as well as view the SPA within AEM.
This document describes the recommended steps to upload a standalone SPA to an AEM instance, add editable sections of content, and enable authoring.
The prerequisites are simple.
First you need to upload the external SPA to your AEM project.
src
in the /ui.frontend
project folder with your React application’s src
folder.package.json
in the /ui.frontend/package.json
file.
/public
folder./public/index.html
file.Now that the external SPA is part of your AEM project, it needs to be configured within AEM.
To take advantage of AEM SPA features, there are dependencies on the following three packages.
@adobe/aem-react-editable-components
@adobe/aem-spa-component-mapping
@adobe/aem-spa-page-model-manager
@adobe/aem-spa-page-model-manager
provides the API for initializing a Model Manager and retrieving the model from the AEM instance. This model can then be used to render AEM components using APIs from @adobe/aem-react-editable-components
and @adobe/aem-spa-component-mapping
.
Run the following npm command to install the required packages.
npm install --save @adobe/aem-spa-component-mapping @adobe/aem-spa-page-model-manager @adobe/aem-react-editable-components
Before the app renders, the ModelManager
needs to be initialized to handle the creation of the AEM ModelStore
.
This needs to be done within the src/index.js
file of your application or wherever the root of the application is rendered.
For this, we can use initializationAsync
API provided by the ModelManager
.
The following screenshot shows how to enable initialization of the ModelManager
in a simple React application. The only constraint is that initializationAsync
needs to be called before ReactDOM.render()
.
In this example, the ModelManager
is initialized and an empty ModelStore
is created.
initializationAsync
can optionally accept an options
object as a parameter:
path
- On initialization, the model at the defined path is fetched and stored in the ModelStore
. This can be used to fetch the rootModel
at initialization if needed.modelClient
- Allows providing a custom client responsible for fetching the model.model
- A model
object passed as a parameter typically populated when using SSR.Create/identify an AEM component for which an authorable React component will be created. In this example, we are using the WKND project’s text component.
Create a simple React text component in the SPA. In this example, a new file Text.js
has been created with the following content.
Create a configuration object to specify the attributes required for enabling AEM editing.
resourceType
is mandatory to map the React component to the AEM component and enable editing when opening in the AEM editor.Use the wrapper function withMappable
.
This wrapper function maps the React component to the AEM resourceType
specified in the config and enables editing capabilities when opened in the AEM editor. For standalone components, it will also fetch the model content for the specific node.
In this example there are separate versions of the component: AEM wrapped and unwrapped React components. The wrapped version needs to be used when explicitly using the component. When the component is part of a page, you can continue using the default component as currently done in the SPA editor.
Render content in the component.
The JCR properties of the text component appear as follows in AEM.
These values are passed as properties to the newly-created AEMText
React component and can be used to render the content.
import React from 'react';
import { withMappable } from '@adobe/aem-react-editable-components';
export const TextEditConfig = {
// Empty component placeholder label
emptyLabel:'Text',
isEmpty:function(props) {
return !props || !props.text || props.text.trim().length < 1;
},
// resourcetype of the AEM counterpart component
resourceType:'wknd-spa-react/components/text'
};
const Text = ({ text }) => (<div>{text}</div>);
export default Text;
export const AEMText = withMappable(Text, TextEditConfig);
This is how the component will appears when the AEM configurations are complete.
const Text = ({ cqPath, richText, text }) => {
const richTextContent = () => (
<div className="aem_text" id={cqPath.substr(cqPath.lastIndexOf('/') + 1)} data-rte-editelement dangerouslySetInnerHTML={{__html: text}}/>
);
return richText ? richTextContent() : (<div className="aem_text">{text}</div>);
};
In this example, we have made further customizations to the rendered component to match the existing text component. This however is not related to authoring in AEM.
Once the authorable React components are created, we can use them throughout the application.
Let’s take an example page where we need to add a text from the WKND SPA project. For this example, we want to display the text “Hello World!” on /content/wknd-spa-react/us/en/home.html
.
Determine the path of the node to be displayed.
pagePath
: The page which contains the node, in our example /content/wknd-spa-react/us/en/home
itemPath
: Path to the node within the page, in our example root/responsivegrid/text
Add component at required position in the page.
The AEMText
component can be added at the required position within the page with pagePath
and itemPath
values set as properties. pagePath
is a mandatory property.
We can now test the component on our running AEM instance.
aem-guides-wknd-spa
directory to build and deploy the project to AEM.mvn clean install -PautoInstallSinglePackage
http://<host>:<port>/editor.html/content/wknd-spa-react/us/en/home.html
.The AEMText
component is now authorable on AEM.
Identify a page to be added for authoring in the SPA. This example uses /content/wknd-spa-react/us/en/home.html
.
Create a new file (for example, Page.js
) for the authorable Page Component. Here, we can reuse the Page Component provided in @adobe/cq-react-editable-components
.
Repeat step four in the section AEM authorable leaf components. Use the wrapper function withMappable
on the component.
As was done previously, apply MapTo
to the AEM resource types for all the child components within the page.
import { Page, MapTo, withMappable } from '@adobe/aem-react-editable-components';
import Text, { TextEditConfig } from './Text';
export default withMappable(Page);
MapTo('wknd-spa-react/components/text')(Text, TextEditConfig);
In this example we are using the unwrapped React text component instead of the wrapped AEMText
created previously. This is because when the component is part of a page/container and not stand alone, the container will take care of recursively mapping the component and enabling authoring capabilities and the additional wrapper is not needed for each child.
To add an authorable page in the SPA, follow the same steps in the section Add Authorable Components to the Page. Here we can can skip the itemPath
property however.
To verify that the page can be edited, follow the same steps in the section Verify Editing of Text Content on AEM.
The page is now editable on AEM with a layout container and child Text Component.
In the previous examples, we added components to the SPA with existing AEM content. However, there are cases where content has not yet been created in AEM, but needs to be added later by the content author. To accommodate this, the front-end developer can add components in the appropriate locations within the SPA. These components will display placeholders when opened in the editor in AEM. Once the content is added within these placeholders by the content author, nodes are created in the JCR structure and content is persisted. The created component will allow the same set of operations as the stand alone leaf components.
In this example, we are reusing the AEMText
component created previously. We want new text to be added below the existing text component on the WKND home page. The addition of components is the same as for normal leaf components. However, the itemPath
can be updated to the path where the new component needs to be added.
Since the new component needs to be added below the existing text at root/responsivegrid/text
, the new path would be root/responsivegrid/{itemName}
.
<AEMText
pagePath='/content/wknd-spa-react/us/en/home'
itemPath='root/responsivegrid/text_20' />
The TestPage
component looks like the following after adding the virtual component.
Ensure the AEMText
component has its resourceType
set in the configuration to enable this feature.
You can now deploy the changes to AEM following the steps in the section Verify Editing of Text Content on AEM. A placeholder will be displayed for the currently non-existing text_20
node.
When the content author updates this component, a new text_20
node is created at root/responsivegrid/text_20
in /content/wknd-spa-react/us/en/home
.
There are a number of requirements to add virtual leaf components as well as some limitations.
pagePath
property is mandatory for creating a virtual component.pagePath
must exist in the AEM project.itemPath
.itemPath='text_20'
in the previous example, the new node will be created directly under the page i.e. /content/wknd-spa-react/us/en/home/jcr:content/text_20
itemPath
.
root/responsivegrid
must exist so that the new node text_20
can be created there.The ability to add containers, even if the corresponding container is not yet created in AEM, is supported. The concept and approach is similar to virtual leaf components.
The front-end developer can add the container components in appropriate locations within the SPA and these components will display placeholders when opened in the editor in AEM. The author can then add components and their content to the container which will create the required nodes in the JCR structure.
For example, if a container already exists at /root/responsivegrid
and the developer wants to add a new child container:
newContainer
does not yet exist in the AEM.
When editing the page containing this component in AEM, an empty placeholder for a container is displayed into which the author can add content.
Once the author adds a child component to the container, the new container node is created with the corresponding name in the JCR structure.
More components and content can be added to the container now as the author requires and the changes will be persisted.
There are a number of requirements to add virtual containers as well as some limitations.
root/responsivegrid
already exists in the AEM container, then a new container can be created by providing the path root/responsivegrid/newContainer
.root/responsivegrid/newContainer/secondNewContainer
is not possible.If you followed the previous examples, your external SPA is now editable within AEM. However there are additional aspects of your external SPA that you can further customize.
By default, we assume that the React application is rendered inside a div
of element ID spa-root
. If required, this can be customized.
For example, assume we have a SPA in which the application is rendered inside a div
of element ID root
. This needs to be reflected across three files.
In the index.js
of the React application (or where ReactDOM.render()
is called)
In the index.html
of the React application
In the AEM app’s page component body via two steps:
body.html
for the page component.body.html
file.If the external React SPA application has multiple pages, it can use routing to determine the page/component to render. The basic use case is to match the currently active URL against the path provided for a route. To enable editing on such routing enabled applications, the path to be matched against needs to be transformed to accommodate AEM-specific info.
In the following example we have a simple React application with two pages. The page to be rendered is determined by matching the path provided to the router against the active URL. For example, if we are on mydomain.com/test
, TestPage
will be rendered.
To enable editing within AEM for this example SPA, the following steps are required.
Identify the level which would act as the root on AEM.
Create a new page at the required level.
mydomain.com/test
. test
is in the root path of the app. This needs to be preserved when creating the page in AEM as well. Therefore we can create a new page at the root level defined in the previous step.mydomain.com/test
, the new page created must be /path/to/aem/root/test
.Add helpers within SPA routing.
/test
whereas the AEM active path is /wknd-spa-react/us/en/test
. To accommodate the AEM-specific portion of the URL, we need to add some helpers on the SPA side.The toAEMPath
helper provided by @adobe/cq-spa-page-model-manager
can be used for this. It transforms the path provided for routing to include AEM-specific portions when the application is open on an AEM instance. It accepts three parameters:
These values can be set as environment variables for more flexibility.
Verify editing the page in AEM.
test
page. The page content is now rendered and AEM components are editable.The RemotePage component expects that the implementation provides an asset-manifest like the one found here. The RemotePage component, however, has only been tested to work with the React framework (and Next.js via the remote-page-next component), and therefore doesn’t support remotely loading applications from other frameworks, such as Angular.
The following reference material may be helpful to understand SPAs in the context of AEM.