Components Still Render When the Datasource Isn't Published in SitecoreAI

Index
TLDR;
If a datasource item is sitting in a non-final workflow state (Draft, for example), Experience Edge does not omit the fields object from the Layout Service response. It returns the full field schema with empty values. Your withDatasourceCheck() guard sees a fields object, decides everything is fine, and happily renders an empty component onto the page.
This is different from how XM 10.x behaves, and it may or may not be a SitecoreAI bug (DEVEX-6041), and also, per a later clarification from Sitecore Support, the default behavior in SitecoreAI.
There is a config patch to turn it off. There is also the boring answer, which is that your components should have been guarding on actual field values all along.
The Symptom
We had a handful of components that were rendering as empty shells on a page. No text, no link, just the wrapper markup and whatever padding the grid gave it. The datasource items existed, but they had never been approved through workflow.
Here's what the Layout Service actually returned:
"page-content": [ { "uid": "b025d227-ecfe-46d1-8dbcfd49e30f39b1", "componentName": "FlexibleCta", "dataSource": "/sitecore/content/sites/main/Home/About/Data/Some CTA Item", "params": { "GridParameters": "col-12", "FieldNames": "Default", "DynamicPlaceholderId": "11" }, "fields": { "ctaLink": { "value": { "href": "" } }, "copy": { "value": "" }, "heading": { "value": "" } } }]The fields object is there. Every field the template defines is there. The dataSource path is there. Everything a guard would normally look for is present and accounted for; it's just that every single value is an empty string.
Why the Guard Didn't Catch It
The component was doing what every JSS tutorial tells you to do:
export const Default = withDatasourceCheck()(FlexibleCtaDefault);withDatasourceCheck() checks whether the rendering has a datasource and whether fields is present. That's it. It is a check for "did an author forget to set a datasource?"; not a check for "is there anything worth rendering in here?"
Given the payload above, the answer to the first question is yes and the answer to the second is no, so the HOC passes the component through and you get a ghost component on the page.
This is the failure mode I was circling around in Early Returns in React Components without naming it: asking "under what conditions would this component not be displayed?" has to mean the content, not the container.
What Sitecore Support Said
I opened a support ticket on this, mostly because the Edge response looked so obviously wrong compared to the XM 10.x Layout Service response I'd tested against side by side. In XM, an unpublished datasource means fields is simply omitted, and every guard in your codebase works exactly the way you assumed it did.
Sitecore reproduced it. The first substantive update was the interesting one, because it corrected my mental model of what was actually going on:
We found that this behavior is related to the workflow state, rather than publishing restrictions. When a datasource item version is in a non-final workflow state, such as Draft, Experience Edge keeps the template field schema but cannot resolve the version data. As a result, it returns the fields object with empty values instead of omitting it completely.
If the item is fully unpublished through Publishing Restrictions, Experience Edge removes it entirely and omits fields. This matches the XM 10.x behavior you expected.
So there are two distinct "not published" states and they behave completely differently:
| How the item is not published | What Edge returns |
|---|---|
| Non-final workflow state (e.g. Draft) | fields present, all values empty |
| Unpublished via Publishing Restrictions | Item removed entirely, fields omitted |
That distinction matters a lot for testing. If you QA this by setting a publishing restriction, you will never reproduce the bug. You have to leave an item in Draft.
Then it got logged as a real bug:
We have confirmed that returning an empty field skeleton for data source items in a non-final workflow state is not the intended behavior. This issue has been logged as a bug under reference DEVEX-6041.
And then, a few days later, it got un-logged as a real bug:
Following our previous update, we received additional clarification from our product team. After further review, they confirmed that this is the default behavior in SitecoreAI.
To change this behavior and align it with XM 10.x, where the fields property is omitted for unapproved data source items, please apply the configuration patch shared in our previous update.
I'll let you draw your own conclusions!
The Workaround
Sitecore's workaround is to patch AlwaysIncludeEmptyFields to false on the SXA JSS item serializer:
<?xml version="1.0" encoding="utf-8"?><configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <layoutService> <configurations> <config name="sxa-jss"> <rendering type="Sitecore.LayoutService.Configuration.DefaultRenderingConfiguration, Sitecore.LayoutService"> <itemSerializer type="Sitecore.XA.JSS.Foundation.Presentation.Serialization.SxaItemSerializer, Sitecore.XA.JSS.Foundation.Presentation" resolve="true"> <AlwaysIncludeEmptyFields>false</AlwaysIncludeEmptyFields> </itemSerializer> </rendering> </config> </configurations> </layoutService> </sitecore></configuration>This restores the XM 10.x shape: unapproved datasource, no fields property, withDatasourceCheck() does its job again.

Before you paste this in, understand what you're actually changing. AlwaysIncludeEmptyFields is not scoped to unpublished items; it changes empty field serialization across the board for that config. Any component that currently reads fields.someOptionalField.value and expects an empty string is now going to read .value off undefined. If your front end is TypeScript with strict null checks and your field access is properly typed as optional, you'll find these at compile time. If it isn't, you'll find them in production.
Test the whole site after applying it; not just the page that showed you the bug.
The Actual Fix
The config patch treats the symptom. The reason those components broke is that they were guarding on the wrong thing, and that's true regardless of what Edge decides to send.
withDatasourceCheck() answers "is there a datasource?" Your component needs to answer "is there content?" Those are different questions and only one of them determines whether something should render:
const FlexibleCtaDefault = ({ fields }: FlexibleCtaProps): JSX.Element | null => { // Guard on the fields that actually make this component meaningful // A CTA with no heading and no link is not a CTA; it's a div with padding const hasHeading = !!fields?.heading?.value; const hasLink = !!fields?.ctaLink?.value?.href;
if (!hasHeading && !hasLink) { return null; }
return ( // ... );};
export const Default = withDatasourceCheck()(FlexibleCtaDefault);Keep the withDatasourceCheck() HOC. It still catches the missing datasource case and it gives authors the friendly message in Pages. Just don't let it be the only thing standing between an empty item and your production page.
Two things worth deciding deliberately per component:
Which fields are load-bearing? For a Stat component it's probably the number; a stat with a label and no value is worse than nothing. For a CTA it's the link and the heading. This is a content question; not a technical one.
What should happen in edit mode? Returning null in Pages means an author looking at a Draft datasource sees the component vanish from the page they're building, which is its own kind of confusing. Checking sitecoreContext.pageEditing and rendering a placeholder message instead of nothing is usually the kinder call.
This decision interacts with your chrome strategy
In One Key Trick for Building Bulletproof Components That Work in Pages Editor, I make the case for setting editable={false} on your Sitecore field components. I still stand by that, but it's worth knowing what it costs you here.
The [No text in field] chrome placeholder is ugly, and it is also the only thing telling an author that a field is empty. Turn chromes off and an empty field renders as nothing at all, so a Draft datasource looks identical to a component someone styled badly. Your own edit mode guard becomes the only signal the author gets, which is a good argument for actually writing one instead of returning null and moving on.
Go Find the Others
The last thing on the ticket, and the part that took the longest: if two components had this problem, others do too. The empty field payload doesn't throw an error and doesn't log anything; it renders quietly and looks like a CSS bug or a content entry mistake until someone opens the network tab.
Two things that made the sweep tractable:
- Grep for the pattern, not the symptom. Any component whose only guard is
withDatasourceCheck()is a candidate. That's a mechanical search across your component folder and it gets you a real list in about a minute. - Build a QA page with every component on it, all with Draft datasources. A teammate set one up while we were working this ticket and it was worth more than the grep was. Every component on one page, every datasource unapproved, and you can see the ghosts all at once instead of discovering them one support ticket at a time.
That second one is the durable artifact. Components get added forever; the page keeps working. Bonus points if you can automate this. 😉
Takeaways
- Non-final workflow state and publishing restrictions are two different things in SitecoreAI, and Experience Edge treats them completely differently. Test with Draft items, not publishing restrictions.
withDatasourceCheck()checks for a datasource. It does not check for content. Don't let it be your only guard.- The
AlwaysIncludeEmptyFields=falsepatch restores the XM 10.x shape, but it changes empty field serialization site wide. Regression test accordingly. - Empty shell components fail silently. A QA page full of unapproved datasources will find them faster than any code review will.
References
- Sitecore Support ticket
CS0677224, bug referenceDEVEX-6041 - https://doc.sitecore.com/xp/en/developers/latest/sitecore-experience-manager/use-a-patch-file-to-customize-the-sitecore-configuration.html
- Early Returns in React Components
- One Key Trick for Building Bulletproof Components That Work in Pages Editor
- Bulletproof Components (Part 2): Back End
Stay guarded,
-MG





