Integrating AEM Universal Editor with a Next.js App
The AEM Universal Editor lets authors edit content directly inside your existing Next.js experience while still treating AEM as a headless CMS. This guide walks through the core integration steps, assuming:
- AEM as a Cloud Service with Content Fragments and GraphQL
- A Next.js app that already fetches content from AEM
For background on concepts and capabilities, see the Universal Editor introduction and headless use cases in AEM documentation.Universal Editor Introduction · Universal Editor Use Cases and Learning Paths
1. Connect Next.js to AEM Headless (Content Fragments + GraphQL)
First make sure your Next.js app is already consuming AEM headless content:
- Model your content in AEM as Content Fragments and publish your GraphQL endpoint.
- From Next.js, call the GraphQL endpoint (SSR, ISR, or client‑side – any is fine):
// Example: basic GraphQL fetch in Next.js
async function fetchArticles() {
const res = await fetch(
'https://publish-pXXXX-eYYYY.adobeaemcloud.com/graphql/execute.json/my-endpoint',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: '{ articleList { items { _path title body } } }' }),
}
);
return res.json();
}
If you’re not at this stage yet, walk through the AEM Headless tutorials first; they use a React app but the pattern is identical for Next.js.Edit the React app with Universal Editor · React app editing using Universal Editor
2. Bootstrap Universal Editor in your Next.js <head>
Next, you need to tell the Universal Editor how to connect your page to AEM.
In your root layout (e.g., _document.tsx or app/layout.tsx), add the AEM connection meta tag and Universal Editor CORS script to the <head>:
// Example for Next.js _document.tsx
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
render() {
const aemAuthorHost = 'https://author-pXXXX-eYYYY.adobeaemcloud.com';
return (
<Html>
<Head>
{/* Universal Editor connection to AEM Author */}
<meta
name="urn:adobe:aue:system:aemconnection"
content={`aem:${aemAuthorHost}`}
/>
{/* Universal Editor CORS helper */}
<script
src="https://universal-editor-service.adobe.io/cors.js"
async
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
Key points:
- Always point
aemconnectionat Author for editing (aem:https://author-…). - Use
https://universal-editor-service.adobe.io/cors.js(do not use older URLs).
3. Mark editable regions with data-aue-* attributes
The Universal Editor inspects your rendered HTML and uses data-aue-* attributes to know:
- What is editable (component, field, fragment reference)
- Where the content lives in AEM (resource path or fragment path)
- How to render the editing UI (field type, labels, etc.)
A simple example for text stored in a Content Fragment field:
// inside a Next.js component rendering an article teaser
export function ArticleTeaser({ fragmentPath, title }) {
return (
<h2
data-aue-resource={`urn:aemconnection:${fragmentPath}`}
data-aue-type="content-fragment"
data-aue-field="title"
>
{title}
</h2>
);
}
Where:
fragmentPathis the CF path, e.g./content/dam/site/articles/my-articledata-aue-type="content-fragment"tells UE this is a CFdata-aue-field="title"maps to the CF elementtitle
For lists of CFs rendered via GraphQL, you typically:
- Return fragment paths in your query.
- For each rendered item, add
data-aue-resourceanddata-aue-fieldso the editor can jump from DOM → CF field.
The React tutorial shows more variations of these attributes; the same patterns apply to Next.js.Edit the React app with Universal Editor
4. Wire preview vs live correctly (AEM + Next.js)
You’ll usually have two frontends:
- Preview Next.js domain → calls AEM Preview GraphQL
- Live Next.js domain → calls AEM Publish GraphQL
For example:
# Preview deployment (Cloudflare env)
NEXT_PUBLIC_AEM_GRAPHQL=https://preview-pXXXX-eYYYY.adobeaemcloud.com/graphql/execute.json/my-endpoint
# Production deployment
NEXT_PUBLIC_AEM_GRAPHQL=https://publish-pXXXX-eYYYY.adobeaemcloud.com/graphql/execute.json/my-endpoint
Then:
- Authors edit via Universal Editor (Author)
- Click Publish → Preview to fill the Preview tier
- Validate on the preview frontend
- Click Publish → Live to push to Publish (used by your live frontend)
Universal Editor itself always connects to Author; your Next.js app decides whether it reads Preview or Publish when running outside UE.
5. Open your Next.js app in Universal Editor
Once headless integration and instrumentation are in place:
-
Deploy your Next.js app to a URL UE can reach (dev/stage/preview).
-
In AEM, open the Universal Editor (via Sites console or direct URL).
-
Provide the public URL of your Next.js page as the “canvas” URL.
-
The Universal Editor will:
- Load your Next.js page
- Inspect the
data-aue-*attributes - Resolve them back to AEM Content Fragments via
urn:aemconnection:… - Let authors edit fields inline or via the properties rail, and save back to AEM
From the author’s perspective, they’re just editing the live Next.js UI; behind the scenes, the editor maps changes back to Content Fragments.Universal Editor Introduction
6. Where to go deeper
Once the main flow is working, typical next steps are:
- Adding definitions for richer components and models
- Handling authentication for protected environments
- Adding a “preview URL” meta tag if you want UE’s Open page button to use a specific frontend URL
For more detailed examples (including full sample apps), the React + Universal Editor tutorial is the best reference; translate its React patterns directly into your Next.js components and layouts.React app editing using Universal Editor