OutSystems 11.40 Reactive, 54 screens across 3 modules, using Prerender.io for dynamic rendering.
The renderer snapshots pages before screen data loads, so crawlers receive the layout with an empty content area and the module's default title.
The per-screen fix works - a JavaScript node in OnRender:
javascript
if (!window.__prerenderDone && $parameters.DataReady) {
window.__prerenderDone = true;
window.prerenderReady = true;
}
with DataReady being the AND of every aggregate's IsDataFetched on that screen.
Maintaining that across 54 screens is unattractive. Is there a way to do this once from the common Layout Block?
The difficulty is the layout knowing when the current screen's aggregates have finished ,has anyone solved this with a placeholder parameter, a client variable the screens set, or an event?
Closing this with what we found, in case it helps anyone running Prerender.io (or any dynamic-rendering proxy) in front of a Reactive Web App on IIS.
Problem: every page was served to crawlers as the same default screen, so many pages shared the same title and canonical tag. I assumed it was a timing issue and asked about setting window.prerenderReady at layout level. That wasn't the cause.
Root cause: the OutSystems ISAPI filter (OsISAPIFilterx64.dll) rewrites friendly URLs to the module root, e.g. /careers/en → /MyModule_UI/?Language=en. It does this before IIS URL Rewrite rules run. So {UNENCODED_URL}, {REQUEST_URI} and {HTTP_X_ORIGINAL_URL} all hold the rewritten URL, or nothing, by the time the rule evaluates. Every page reached Prerender.io as the same few module-root URLs. A Failed Request Tracing (FREB) capture showed the order clearly.
Fix: the filter keeps the original path in a request header, OS-Page. Read it in the rule as {HTTP_OS_PAGE}:
Rewrite URL: https://service.prerender.io/https://{HTTP_HOST}{HTTP_OS_PAGE}Condition: {HTTP_OS_PAGE} matches ^/
Create it as a global (server-level) rule. Keep the usual crawler user-agent condition, and add {HTTP_X_PRERENDER} does not match 1 so Prerender.io's own fetch doesn't loop back.
One catch with module aliases: for modules served under a path prefix, OS-Page holds only the page part (/About-Us/en, not /MyAlias/About-Us/en). Add a rule per module, placed above the main rule. It should match the module's internal name after the filter (^/?MyModule/?$) and rebuild the URL with the public prefix (…/https://{HTTP_HOST}/MyAlias{HTTP_OS_PAGE}).
Result: all pages now render for Googlebot and Semrush, on desktop and mobile, each under its own URL. In our case prerenderReady wasn't needed to fix the duplicates.
Hello Abdessalem Haddad,
The Layout itself cannot reliably detect that all Aggregates of the current Screen are finished. IsDataFetched and On After Fetch are exposed per Aggregate/Data Action.
For 54 screens, I’d use a small reusable pattern:
Keep the window.prerenderReady logic in the common Layout/Block.
Each Screen exposes a single DataReady Boolean (or sets a shared Client Variable/Event) once its required Aggregates/Data Actions are complete.
The Layout listens to that signal and sets window.prerenderReady = true.
Avoid putting the full Aggregate AND expression or Prerender JavaScript on every screen.
This keeps the Prerender integration centralized while the Screen remains responsible for knowing when its own data is actually ready.
OutSystems confirms that On After Fetch runs when each Aggregate/Data Action finishes, while On Render can occur multiple times as Screen data changes.Thanks and Regards,Akshay Deshpande