Solved! Go to Solution.
Views
Replies
Total Likes
Hi @MayurSatav ,
Root Cause
In AEM SPA Editor, the *.model.json file of a SPA root page (e.g., /en) includes all child pages by design.
So, when you publish a child (like /en/home), /en.model.json remains cached and is not invalidated automatically — leading to stale data unless:
- You manually republish /en, or
- Explicit cache invalidation or re-rendering is triggered.
This is not a bug, but a default behavior of the AEM SPA model export system, which serves the whole content tree from the SPA root.
Solution:
1. Automatically Republish the SPA Root Page on Child Publish
This is the most reliable and scalable method.
Implementation Options:
Create a custom workflow process step or listener that listens to replication events (e.g., com.day.cq.replication.ReplicationAction.EVENT_TOPIC) and automatically replicates the root SPA page (e.g., /en) whenever any of its children are published.
Example:
Listen for any page under /content/mysite/us/en/*, and then trigger replication of /content/mysite/us/en.
"If a child page changes, the root model should be invalidated (or re-fetched) to reflect changes in the aggregated model tree."
2. Force Invalidate .model.json in Dispatcher or CDN
Configure the flush agent or dispatcher to invalidate:
- /en.model.json
- /en.model.json?*
- /en.infinity.json (if used)
You can do this via a custom AgentConfig or Dispatcher flush rule triggered on publish of any descendant.
Example:
# dispatcher/cache.rules
/0001 { /glob "*.model.json" /type "deny" }
/0002 { /glob "/content/mysite/us/en.model.json" /type "allow" }
/0003 { /glob "/content/mysite/us/en/*" /type "allow" }
3. Use Client-side Fetching of Child .model.json (Alternative Pattern)
If you don’t want full model hydration at root level, you can configure the SPA SDK to fetch each page's own .model.json rather than aggregating via the root.
This requires modifying the ModelManager config in the SPA (React):
ModelManager.initialize({
path: window.location.pathname + ".model.json"
});
But be careful — this breaks deep linking and SPA routing unless handled very carefully. The default root-model pattern is generally safer and better supported.
4. Prevent Double Rendering in SPA
This issue usually occurs when:
Initial model (en.model.json) contains all child pages, and
SPA route renders both current and nested components.
Fix:
Ensure your React routing is scoped strictly to path match and not rendering nested child routes unless explicitly required.
<Switch>
<Route exact path="/en" component={EnPage} />
<Route exact path="/en/home" component={HomePage} />
</Switch>
Note:
Stick with the root model approach, and use automatic replication of the root page (or its .model.json invalidation) as your scalable fix. This is used in enterprise-grade AEM SPA setups across AMS and Cloud.
Regards,
Amit
Hi @MayurSatav,
I believe the cause of this issue in AEM SPA projects is that the *.model.json of a parent page (e.g., en.model.json) aggregates data from its child pages. When a child page is published, the parent’s model isn’t automatically refreshed or invalidated. This usually happens due to:
Here are a few solutions you can try:
1. Auto-replicate the Parent Page – Implement an event listener or custom workflow step to automatically republish the parent page (e.g., /en) whenever any of its child pages is published.
2. Dispatcher Cache Invalidation – Update your flush agent configuration to explicitly invalidate the parent .model.json (e.g., /en.model.json) whenever a child page is published.
3. Avoid Caching .model.json Aggressively – Adjust Dispatcher rules to reduce or prevent caching of .model.json files, or set a lower TTL to ensure timely updates.
Try these and let me know if something works.
Thanks.
Hi @ShivamKumar ,Thanks for your prompt response.
I've already tried the solutions you shared, but none of them worked — except for the first one. I'm specifically trying to understand why the model isn't updating automatically if we're using a React SPA. In your first suggestion, you mentioned auto-replicating the root page. Is that considered a recommended practice?
Also, in an AEM React SPA setup, shouldn't each page have its own model.json
, or is it expected that all pages are served under a single en.model.json
?
Interestingly, if I set the SPA template's root page as the entry point, the cache issue gets resolved. However, when I navigate to another page, the rendered output includes content from both pages, leading to double rendering on the same view.
Any thoughts or best practices to handle this more cleanly?
Views
Replies
Total Likes
Hi @MayurSatav ,
Root Cause
In AEM SPA Editor, the *.model.json file of a SPA root page (e.g., /en) includes all child pages by design.
So, when you publish a child (like /en/home), /en.model.json remains cached and is not invalidated automatically — leading to stale data unless:
- You manually republish /en, or
- Explicit cache invalidation or re-rendering is triggered.
This is not a bug, but a default behavior of the AEM SPA model export system, which serves the whole content tree from the SPA root.
Solution:
1. Automatically Republish the SPA Root Page on Child Publish
This is the most reliable and scalable method.
Implementation Options:
Create a custom workflow process step or listener that listens to replication events (e.g., com.day.cq.replication.ReplicationAction.EVENT_TOPIC) and automatically replicates the root SPA page (e.g., /en) whenever any of its children are published.
Example:
Listen for any page under /content/mysite/us/en/*, and then trigger replication of /content/mysite/us/en.
"If a child page changes, the root model should be invalidated (or re-fetched) to reflect changes in the aggregated model tree."
2. Force Invalidate .model.json in Dispatcher or CDN
Configure the flush agent or dispatcher to invalidate:
- /en.model.json
- /en.model.json?*
- /en.infinity.json (if used)
You can do this via a custom AgentConfig or Dispatcher flush rule triggered on publish of any descendant.
Example:
# dispatcher/cache.rules
/0001 { /glob "*.model.json" /type "deny" }
/0002 { /glob "/content/mysite/us/en.model.json" /type "allow" }
/0003 { /glob "/content/mysite/us/en/*" /type "allow" }
3. Use Client-side Fetching of Child .model.json (Alternative Pattern)
If you don’t want full model hydration at root level, you can configure the SPA SDK to fetch each page's own .model.json rather than aggregating via the root.
This requires modifying the ModelManager config in the SPA (React):
ModelManager.initialize({
path: window.location.pathname + ".model.json"
});
But be careful — this breaks deep linking and SPA routing unless handled very carefully. The default root-model pattern is generally safer and better supported.
4. Prevent Double Rendering in SPA
This issue usually occurs when:
Initial model (en.model.json) contains all child pages, and
SPA route renders both current and nested components.
Fix:
Ensure your React routing is scoped strictly to path match and not rendering nested child routes unless explicitly required.
<Switch>
<Route exact path="/en" component={EnPage} />
<Route exact path="/en/home" component={HomePage} />
</Switch>
Note:
Stick with the root model approach, and use automatic replication of the root page (or its .model.json invalidation) as your scalable fix. This is used in enterprise-grade AEM SPA setups across AMS and Cloud.
Regards,
Amit
Thanks for the detailed explanation @AmitVishwakarma!, So essentially, only the en.model.json
(our SPA root) should be rendered and not individual page JSONs like home.model.json
or about.model.json
, correct?
Views
Replies
Total Likes
Hi @MayurSatav ,
Yes, that's correct. In the default AEM SPA setup with the root model approach, the SPA relies on the root en.model.json to hydrate the entire app, including all child pages. This is why individual page .model.json files like home.model.json or about.model.json are not fetched separately.
This approach ensures the SPA routing remains consistent and SEO-friendly, but it also means you need to handle cache invalidation or auto-replication of the root page carefully to reflect child page updates.
Regards,
Amit
so currently for me cache invalidation is not working and before moving for the auto replication. Frontend team has updated routing from index.tsx. Which resolved the cacheing issue. I'm just understanding the the process as this is my first time to work on AEM+REACT.
// @TS-nocheck
import "react-app-polyfill/stable";
import "react-app-polyfill/ie9";
import "custom-event-polyfill";
import { Constants, ModelManager } from "@adobe/aem-spa-page-model-manager";
import { createBrowserHistory } from "history";
import React from "react";
import { createRoot } from "react-dom/client";
import { Router } from "react-router-dom";
import App from "./App";
import LocalDevModelClient from "./LocalDevModelClient";
import "./components/import-components";
import "./styles/index.scss";
const THEME_CLASS = {
light: "theme-nc-default", // Light theme class
dark: "theme-nc-reversed", // Dark theme class
};
const modelManagerOptions = {};
if (process.env.REACT_APP_PROXY_ENABLED) {
modelManagerOptions.modelClient = new LocalDevModelClient(
process.env.REACT_APP_API_HOST
);
}
const renderApp = () => {
const history = createBrowserHistory();
const updatePageModel = () => {
ModelManager.initialize(modelManagerOptions).then((pageModel) => {
console.log("Updated page model:", pageModel);
const root = createRoot(document.getElementById("spa-root"));
root.render(
<Router history={history}>
<App
history={history}
cqChildren={pageModel[Constants.CHILDREN_PROP]}
cqItems={pageModel[Constants.ITEMS_PROP]}
cqItemsOrder={pageModel[Constants.ITEMS_ORDER_PROP]}
cqPath={pageModel[Constants.PATH_PROP]}
locationPathname={window.location.pathname}
/>
</Router>
);
});
};
// Initialize the app for the first time
updatePageModel();
// Listen for route changes and reinitialize the page model
history.listen((location) => {
console.log("Route changed to:", location.pathname);
updatePageModel();
});
document.documentElement.classList.add(THEME_CLASS.light); // Add class to <html> tag
};
document.addEventListener("DOMContentLoaded", () => {
renderApp();
});
Views
Replies
Total Likes
Hi @MayurSatav ,
Thanks for the detailed update and great to hear that the frontend routing adjustment helped resolve the caching issue on navigation.
Looking at the code snippet, it’s clear your team implemented a runtime model refresh by calling ModelManager.initialize() inside the history.listen() callback.
That’s a smart move, especially when dealing with stale model.json responses in SPAs. It essentially re-fetches the model data every time the route changes, which helps keep things up-to-date on client-side navigation even without needing to manually republish the root page or flush caches right away.
So yes, to confirm:
- You’re still leveraging the root model pattern (en.model.json).
- But now there’s an additional mechanism to reinitialize the model dynamically during navigation.
- This definitely helps mitigate stale content during runtime navigation.
A couple of things to watch out for as you move forward:
1. Model Fetch Behavior
Just keep an eye on which endpoint is actually being fetched by ModelManager. If a user navigates to /en/home, and the model call goes to /en/home.model.json instead of /en.model.json, it could indicate a shift from the root model strategy to a per-page model fetch. That’s fine if intentional, but worth confirming via the network tab or logs.
2. Initial Load Consideration
This setup works great during SPA navigation, but if a user directly lands on a child URL like /en/home, the first model fetch still depends on whatever is cached (CDN or dispatcher). If that cache isn’t invalidated after a child publish, you might still hit stale data on that first load.
3. Long-Term Strategy
In most of the AEM SPA setups I’ve worked on, we’ve eventually added auto-replication of the root page (/en) whenever a child is published. It just makes things predictable and avoids manual steps or routing workarounds for cache invalidation. Might be worth exploring once things stabilize.
Regards,
Amit
Thank you so much @AmitVishwakarma