For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt, and this page is available as Markdown at https://nhanbe.id.vn/aem/ui/spa-editor.md.

AEM SPA Editor - developer documentation and reference notes for AEM SPA Editor.

AEM SPA Editor

AEM SPA Editor cho phép content author chỉnh sửa trực quan Single Page Applications (React hoặc Angular) ngay trong môi trường AEM Author — kéo thả component, inline editing, layout mode, đầy đủ tính năng authoring quen thuộc từ AEM Pages truyền thống.

SPA render từ JSON model do AEM cung cấp, và SPA Editor SDK làm cầu nối giữa SPA framework và authoring layer của AEM.

AEM 6.5

SPA Editor được hỗ trợ từ AEM 6.4 SP2 trở đi. Trên AEM 6.5, cần Service Pack 6 trở lên để có đầy đủ tính năng ổn định. Kiến trúc và API ở đây áp dụng cho cả AEM 6.5 và AEMaaCS.


1. Cơ chế hoạt động

Luồng kiến trúc

5 bước cốt lõi:

  1. AEM serve JSON model của page qua Sling Model Exporter (endpoint .model.json)
  2. SPA đọc JSON và render các React/Angular component tương ứng
  3. SPA Editor SDK bổ sung authoring overlays (selection, drag targets, placeholder) lên trên SPA
  4. Edits từ Author UI được ghi xuống JCR → JSON model cập nhật → SPA re-render
  5. Trên Publish, SPA render từ cùng JSON nhưng không có editor overlay nào
Browser (Author)
    │
    ▼
AEM SPA Editor Frame
    ├── Editor Overlays (authoring SDK)
    └── SPA iframe
          │
          ▼
    ModelManager → GET /content/myproject/home.model.json
          │
          ▼
    React Component Tree (renders from JSON)

Cấu trúc JSON model

Page model là một JSON tree lồng nhau. Mỗi component map với một JCR node:

{
  ":type": "myproject/components/page",
  ":path": "/content/myproject/en/home",
  ":children": {
    "root": {
      ":type": "wcm/foundation/components/responsivegrid",
      ":items": {
        "hero": {
          ":type": "myproject/components/hero",
          "title": "Welcome",
          "backgroundImage": "/content/dam/myproject/hero.jpg"
        },
        "cards": {
          ":type": "myproject/components/cards",
          ":items": {}
        }
      },
      ":itemsOrder": ["hero", "cards"]
    }
  }
}

:type là key kết nối JSON node với React component thông qua MapTo.


2. Project Setup

Tạo project từ AEM Archetype

AEM Project Archetype sinh ra project SPA hoàn chỉnh khi chọn frontendModule=react:

mvn -B archetype:generate \
  -DarchetypeGroupId=com.adobe.aem \
  -DarchetypeArtifactId=aem-project-archetype \
  -DarchetypeVersion=49 \
  -DappTitle="My SPA Project" \
  -DappId="myspa" \
  -DgroupId="com.myproject" \
  -DfrontendModule=react

Cấu trúc được sinh ra:

myspa/
├── core/               # Java backend (Sling Models, Exporters)
├── ui.apps/            # AEM components, templates, clientlibs
├── ui.content/         # Sample content
├── ui.config/          # OSGi configs
└── ui.frontend/        # React SPA
    ├── src/
    │   ├── components/ # React components mapped tới AEM components
    │   ├── App.js
    │   └── index.js
    ├── package.json
    └── webpack.config.js

Các npm package quan trọng

PackageVai trò
@adobe/aem-react-editable-componentsMapTo, Page, withModel, ResponsiveGrid
@adobe/aem-spa-page-model-managerModelManager, ModelClient, AuthoringUtils
@adobe/aem-spa-component-mappingComponent registry cấp thấp
@adobe/aem-core-components-react-baseReact wrappers cho Core Components
@adobe/aem-core-components-react-spaSPA-specific Core Component wrappers
npm install @adobe/aem-react-editable-components \
  @adobe/aem-spa-page-model-manager \
  @adobe/aem-spa-component-mapping
Phiên bản

Trên AEM 6.5, dùng @adobe/aem-react-editable-components@^1.x và React 16/17. Phiên bản 2.x nhắm tới AEMaaCS và React 18. Kiểm tra compatibility matrix trước khi nâng cấp.


3. Component Mapping với MapTo

MapTo() là khái niệm trung tâm: kết nối một React component với một AEM resource type. Khi JSON model có node với :type = "myproject/components/hero", SPA render React component đã được map.

Mapping cơ bản

import React from 'react';
import { MapTo } from '@adobe/aem-react-editable-components';

const HeroEditConfig = {
  emptyLabel: 'Hero',
  isEmpty: function(props) {
    return !props || !props.title;
  }
};

const Hero = ({ title, backgroundImage, ctaText, ctaLink }) => (
  <section
    className="hero"
    style={{ backgroundImage: `url(${backgroundImage})` }}
  >
    <div className="hero-content">
      <h1>{title}</h1>
      {ctaText && (
        <a href={ctaLink} className="hero-cta">{ctaText}</a>
      )}
    </div>
  </section>
);

export default MapTo('myproject/components/hero')(Hero, HeroEditConfig);

Cơ chế hoạt động

  1. JSON model chứa field :type cho từng component
  2. MapTo('myproject/components/hero')(HeroComponent, editConfig) đăng ký mapping vào registry
  3. Khi Page Model Manager gặp resource type đó trong JSON, nó render React component tương ứng
  4. editConfig cho editor biết cách xử lý trạng thái rỗng và placeholder

Các tùy chọn editConfig

Tùy chọnKiểuMô tả
emptyLabelstringText hiển thị trong placeholder khi component rỗng
isEmpty(props)function → booleanTrả về true khi component chưa có nội dung
resourceTypestringOverride resource type nếu khác với MapTo key

4. Container Components

Container component (như Responsive Grid) chứa các child component. Chúng nhận :items và :itemsOrder từ JSON model.

Map Responsive Grid chuẩn

import { MapTo, ResponsiveGrid } from '@adobe/aem-react-editable-components';

MapTo('wcm/foundation/components/responsivegrid')(ResponsiveGrid);

Custom container

Với container tùy chỉnh (tabbed section, accordion...), cần implement phía backend ContainerExporter và frontend nhận :items/:itemsOrder:

import React from 'react';
import { MapTo, withComponentMappingContext } from '@adobe/aem-react-editable-components';

const TabContainer = ({ cqItems, cqItemsOrder }) => {
  const [activeTab, setActiveTab] = React.useState(0);

  return (
    <div className="tab-container">
      <div className="tab-nav">
        {cqItemsOrder.map((key, idx) => (
          <button key={key} onClick={() => setActiveTab(idx)}>
            Tab {idx + 1}
          </button>
        ))}
      </div>
      <div className="tab-content">
        {cqItemsOrder[activeTab] && cqItems[cqItemsOrder[activeTab]]}
      </div>
    </div>
  );
};

export default MapTo('myproject/components/tab-container')(
  withComponentMappingContext(TabContainer)
);

5. Page Component (Entry Point)

App.js — Root component

import React from 'react';
import { Page, withModel } from '@adobe/aem-react-editable-components';

import './components/Hero/Hero';
import './components/Cards/Cards';
import './components/Text/Text';

function App() {
  return <Page />;
}

export default withModel(App);

withModel là HOC tự động fetch page model và truyền xuống dưới dạng props. Import các component file ở đây để đảm bảo MapTo được gọi và đăng ký vào registry trước khi render.

index.js — Entry point

import React from 'react';
import ReactDOM from 'react-dom';
import { ModelManager } from '@adobe/aem-spa-page-model-manager';
import App from './App';

ModelManager.initialize().then((pageModel) => {
  ReactDOM.render(
    <App
      cqChildren={pageModel[':children']}
      cqItems={pageModel[':items']}
      cqItemsOrder={pageModel[':itemsOrder']}
      cqPath={pageModel[':path']}
    />,
    document.getElementById('spa-root')
  );
});

ModelManager.initialize() fetch JSON model từ AEM và trả về pageModel để boot SPA.


6. Backend: Sling Model Exporter

Mỗi AEM component xuất hiện trong SPA cần một Sling Model Exporter sinh ra JSON representation tương ứng.

import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

@Model(
    adaptables = SlingHttpServletRequest.class,
    adapters = { HeroModel.class, ComponentExporter.class },
    resourceType = HeroModel.RESOURCE_TYPE
)
@Exporter(
    name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
    extensions = ExporterConstants.SLING_MODEL_EXTENSION
)
public class HeroModel implements ComponentExporter {

    static final String RESOURCE_TYPE = "myproject/components/hero";

    @ValueMapValue(optional = true)
    private String title;

    @ValueMapValue(optional = true)
    private String backgroundImage;

    @ValueMapValue(optional = true)
    private String ctaText;

    @ValueMapValue(optional = true)
    private String ctaLink;

    public String getTitle()           { return title; }
    public String getBackgroundImage() { return backgroundImage; }
    public String getCtaText()         { return ctaText; }
    public String getCtaLink()         { return ctaLink; }

    @Override
    public String getExportedType() { return RESOURCE_TYPE; }
}

Hai điểm quan trọng:

  • @Exporter + implements ComponentExporter → model được include vào page JSON
  • getExportedType() trả về resource type → map ngược lại với :type trong JSON

Kiểm tra JSON tại: http://localhost:4502/content/myproject/en/home.model.json


7. SPA Routing và Navigation

SPA xử lý client-side routing. SPA Editor SDK tích hợp với React Router:

import { BrowserRouter as Router, Route } from 'react-router-dom';
import { Page } from '@adobe/aem-react-editable-components';
import { ModelManager } from '@adobe/aem-spa-page-model-manager';

function App() {
  return (
    <Router>
      <Route
        path="/:pagePath*"
        render={(routeProps) => (
          <Page
            cqPath={routeProps.match.url}
            {...routeProps}
          />
        )}
      />
    </Router>
  );
}

Khi user navigate sang page mới:

  1. React Router xử lý URL change
  2. ModelManager fetch JSON model cho page mới (nếu chưa cache)
  3. Page component re-render với model mới
  4. Trong author mode, editor overlays cập nhật tự động

8. Remote SPA

Remote SPA là SPA hosted bên ngoài AEM (Vercel, Netlify, CDN tùy chỉnh) nhưng vẫn có thể edit trong AEM SPA Editor thông qua iframe.

Cấu hình

  1. Host SPA trên external server
  2. Cấu hình AEM cho phép remote origin (CORS, CSP headers)
  3. SPA fetch page model từ AEM JSON endpoint
  4. SPA Editor load remote SPA trong iframe để authoring
ModelManager.initialize({
  modelClient: new ModelClient('https://author.myproject.com')
});

Khi nào dùng Remote SPA

Trường hợpKhuyến nghị
SPA đã có, muốn tích hợp AEM authoring✅ Remote SPA
CI/CD pipeline SPA độc lập với AEM deploy✅ Remote SPA
SPA mới, build từ đầu với AEMEmbedded SPA (ui.frontend)
Cần full control authoring experienceEmbedded SPA

9. SPA vs Headless vs Edge Delivery

Tiêu chíSPA EditorHeadless (GraphQL)Edge Delivery
AuthoringWYSIWYG đầy đủJSON/CF EditorDocument authoring
Frontend techReact/Angular SDKBất kỳ frameworkHTML/JS thuần
Tích hợp AEMChặt chẽ (SDK)Lỏng (API)Git-based
Build complexityCaoTrung bìnhThấp
Publish performancePhụ thuộc SPACDN-friendlyRất cao (edge)
Phù hợpTeam đã quen AEM + muốn WYSIWYG cho SPAOmnichannel, nhiều consumersMarketing sites tốc độ cao

10. Authoring Experience

Edit mode

Author thấy SPA với blue overlays trên mỗi editable component:

  • Click để select component
  • Component toolbar: edit, configure, delete, move
  • Kéo component từ side panel vào trang
  • Inline-edit text component trực tiếp

Layout mode

Author resize component bên trong responsive grid, giống AEM Pages truyền thống.

Preview mode

Render SPA không có editor overlay — đúng như visitor sẽ thấy.

Placeholder handling

Khi component rỗng, SPA Editor SDK hiển thị placeholder dựa vào emptyLabel từ edit config:

const TextEditConfig = {
  emptyLabel: 'Text -- click to edit',
  isEmpty: (props) => !props || !props.text || props.text.trim().length === 0
};

11. Common Patterns

Conditional authoring scaffolding

Hiển thị markup bổ sung chỉ trong edit mode để guide author:

import { AuthoringUtils } from '@adobe/aem-spa-page-model-manager';

const MyComponent = (props) => {
  const isInEditor = AuthoringUtils.isInEditor();

  return (
    <div className="my-component">
      {isInEditor && (
        <div className="author-hint">Kéo items vào đây</div>
      )}
      {/* Component content */}
    </div>
  );
};

Map Core Components

Tái sử dụng AEM Core Component React wrappers:

import { TitleV2IsEmptyFn } from '@adobe/aem-core-components-react-base';
import { MapTo } from '@adobe/aem-react-editable-components';

const Title = ({ text, type }) => {
  const HeadingTag = type || 'h2';
  return <HeadingTag>{text}</HeadingTag>;
};

MapTo('myproject/components/title')(Title, {
  emptyLabel: 'Title',
  isEmpty: TitleV2IsEmptyFn
});

12. Best Practices

  • Map mọi editable component — component thiếu MapTo sẽ tạo ra vùng trắng trong editor, không có thông báo lỗi
  • isEmpty() chính xác — placeholder chỉ xuất hiện khi component thực sự rỗng; implement sai gây confuse author
  • Test cả hai mode — SPA hoạt động khác nhau giữa author mode (có overlay) và publish mode (render sạch)
  • Không thao tác DOM trực tiếp — jQuery hay document.querySelector xung đột với editor overlay, gây rendering glitch
  • Toàn bộ editable content qua JSON model — không fetch data client-side cho content cần authoring; mọi content phải đi qua Sling Model Exporter

13. Common Pitfalls

Vấn đềNguyên nhânCách sửa
Component trắng trong editorThiếu MapTo hoặc resource type saiKiểm tra :type trong JSON khớp với MapTo key
Placeholder luôn hiển thị dù có contentisEmpty() logic saiDebug props thực tế, đảm bảo field name đúng
Layout mode không hoạt độngThiếu map ResponsiveGridThêm MapTo('wcm/foundation/components/responsivegrid')(ResponsiveGrid)
JSON model thiếu componentSling Model không implement ComponentExporterThêm @Exporter và implements ComponentExporter
SPA không load trong Authorpage component thiếu sling:resourceSuperTypeSet sling:resourceSuperType = core/wcm/components/page/v3/page
ModelManager lỗi initGọi trước khi DOM ready hoặc thiếu configGọi ModelManager.initialize() trong entry point, sau DOM ready
Lỗi CORS khi Remote SPAAEM chưa whitelist remote originCấu hình com.adobe.granite.cors.impl.CORSPolicyImpl

Tham khảo