We have personalized content on our pages (e. g. recommendations for articles or a home location selected by the user). This content is fetch client side to have it individual based on the user.
In the context of this idea the content are lists of teasers (with a link to an article detail page) e.g. on homepages.
For server-side rendering and as a fallback if no personalized teasers are available (e.g. due to technical failure) we use non-personalized teasers from the Content API to render the articles teasers. These teasers get replaced by the personalized content once its available.
For all our server-side rendered teaser lists (not only personalized lists) we use the <Static> wrapper together with staticMode=true in the useContent call. This way the lists don't need to be rendered client-side and we can save HTML-size by not including the article data.
If there is personalized content, we remove the static content from server-side (the content that is wrapped in <Static>) and replace it with the client-side rendered personalized teaser list.
This works fine if we know this during the first render pass and can directly remove the static content. However, it does not work in cases where we want to remove it after the first render pass. Then the enter and exit divs are removed, but the actual content inside of the <Static> wrapper are not removed as expected.
Use cases for this are:
Personalized recommendations are only fetched after the use has consented to the CMP terms. Once fetched the personalized recommendations can replace the static content.
The user has initially on home location set. Once he selects a home location, we fetch the latest news for this location and replace the static content with the specific location content.
Here is a short reproduction example of the static content removal after the first render pass:import { JSX, useEffect, useState } from 'react';
import Static from 'fusion:static';
import { FeatureProps } from '~types/fusion/FeatureProps';
function StaticTest(props: FeatureProps): JSX.Element {
const [shouldShowStaticContent, setShouldShowStaticContent] = useState(true);
useEffect(() => {
// This is a dummy effect to reproduce the bug. In a real scenario, there is some more logic.
setShouldShowStaticContent(false);
}, []);
return (
<>
{shouldShowStaticContent && (
<Static id={props.id}>
<p>Static content</p>
</Static>
)}
<p>Dynamic content</p>
</>
);
}
StaticTest.label = 'StaticTest';
export default StaticTest;
Since I thought this is a bug in PageBuilder Engine, I created this ACS ticket: https://arcpublishing.atlassian.net/servicedesk/customer/portal/2/ACS-38719. There I was told to create an idea instead. Please let me know if this is not the correct place.
Hi Julian, thanks for the detailed write-up and the repro. It made this easy to trace.
Here is how Static works. On the server, Engine renders the Static content and wraps it in two hidden marker elements. In the browser, Engine uses those markers to find the original HTML nodes and save them before React renders, then puts the nodes back in place afterward. That is what lets Static content survive the client render, and with the
persistentoption it also survives client-side navigation between pages, for example a player that should keep playing. The trade-off is that the children inside Static never run in the browser. Hooks, state and event handlers in components rendered inside Static do not execute, and React doesn't manage those nodes.Your repro shows the effect of that. The
useEffectin your feature component does run, because it sits outside Static. It removes Static from the React tree, which removes the two marker elements. The saved HTML nodes are not part of the React tree, so they stay. Removing Static in the first render pass works only because nothing puts the nodes back. I agree it looks inconsistent from the outside.Our first read was that this was a feature request for Static. After a closer look with engineering team about how this idea can be implemented in Static, we see it contradicts with Static features design too. Static is built to keep server output frozen with no client-side logic, adding this would make Static components mutable which is not what its designed for. So Static isn't the right tool for content that needs to change in the browser. The opposite approach fits your use case better: keep the server render as the fallback, and let the client decide when to replace it with personalized content.
Here is a pattern that works with Static as it is. Keep the fallback in Static, render the personalized list as a client-only sibling, and hide the fallback with an element React owns:
The fallback stays in the DOM, hidden, so the page weight is the same as today and no article data is added to the client. It also still shows if the personalized fetch fails. Because the server never receives user-specific data, the CDN cache stays safe to share.
Because Static isn't designed for this, we aren't planning to change it, so I'm marking this idea as Will not implement. If the pattern doesn't fit your case, reply here with what you see and we'll take another look.