Experience Editor wasn't loading my components on the QA server, but everything was loading fine on my local machine (how typical). I needed to figure out what was causing the issue and how to solve it.
Tech Specs: @sitecore-jss/sitecore-jss-nextjs
→ v21.5.0
Sitecore Product: XM Cloud
→ v1.5.278
Recently, we were building out a multistep custom form for a client using XM Cloud that required the entire form to be editable inside Sitecore Experience Editor. While architecting out the solution for our humongous form, I decided the most optimal solution would be to dynamically import (lazy load) each step of the form only when the user needs it. Since the form had some steps that users would never see, it seemed almost obvious that this was the way forward.
I created Dynamic imports for each part of my form, and set a sitecoreContext.pageEditing
flag on each step, which would allow the client to author each page inside of Experience Editor.
While developing on my local machine, I had no issues using the form on the front end or editing values inside the Experience Editor. However, once we had finished developing the form and pushed it to our QA server, none of the subcomponents were loading inside the Experience Editor.
After some digging, adding in some cheesy console.log
's to test what was/wasn't loading, I realized that perhaps next/dynamic
was causing issues with Sitecore and XM Cloud. After converting the dynamic imports back to traditional imports - everything loaded perfectly inside of Experience Editor!
There doesn't seem to be much (or any) documentation about using Next/Dynamic
with Sitecore, which is upsetting because it's an important way to code split your Next.js solution. Luckily, I've only found this issue happening when I'm trying to conditionally render top level components that need to be edited in Experience Editor. I'm going to go through a few common use cases for Next/Dynamic
and highlight where I have yet to find any issues. This way we can still get some code splitting benefits in our Sitecore JSS projects!
There is a new way to lazy load components that easily documented, and its actually even easier to implement than the traditional way of importing dynamic in your components. Simply apply the suffix .dynamic to your file name, and the componentBuilder
will auto-detect and dynamically load your components for you!
So when a component file is created (ex: MyComponent.dynamic.tsx
) it will be loaded as dynamic()
via src/temp/componentBuilder.ts
. This is how it’s imported in componentBuilder.ts:
const GraphQLConnectedDemo = {
module: () => import('src/components/graphql/GraphQL-ConnectedDemo.dynamic'),
element: (isEditing?: boolean) => isEditing ? require('src/components/graphql/GraphQL-ConnectedDemo.dynamic')?.default : dynamic(GraphQLConnectedDemo.module)
}
If you want even more context, I found the original Pull Request that implemented this feature. [Next.js] Enable dynamic component import in sample by illiakovalenko · Pull Request #727 · Sitecore/jss
Needless to say, this is where I was having all my problems: Lazy Loading a component that is directly used in the parent. The key areas I've found this failing is when the parent is directly linked to a Sitecore rendering item, and we are doing a full lazy load import (like in the image example above). To prevent Lazy Loading using Next/Dynamic
, you will want to avoid using conditional renders for any components imported using dynamic()
.
Code Example:
import dynamic from 'next/dynamic';
const CustomSitecoreComponent = dynamic(
() => import('child-component/CustomSitecoreComponent')
);
...
{/* AVOID CONDITIONAL RENDERS LIKE: */}
{!isDisplayCustomComponent && (
<div className={containerStyles}>
<CustomSitecoreComponent fields={props.fields} />
</div>
)}
{/* ALSO AVOID TURNARIES LIKE: */}
{!isDisplayCustomComponent ? (
<div className={containerStyles}>
<CustomSitecoreComponent fields={props.fields} />
</div>
) : (
<></>
)}
Note: If your eagle- eye noticed I am importing from a /child-component/
folder instead of the normal /component/
folder and want to know why, give this blog post a read that explains the importance of splitting out your components.
Since all the problems come from Authoring inside Experience Editor, it should be obvious that lazy loading components that don't have any Sitecore editable fields (only hard-coded or API returned values) won't cause any problems. In case you're slightly confused what the difference is, here is a code example:
Code Example:
{/* Skeleton to display while loading the CreditCard data */}
{!CardData && (
<div className="h-[180px] w-[247px] md:h-[234px] md:w-[352px]">
<Skeleton count={1} height={180} />
</div>
)}
{/* Conditionally render the users Credit Card */}
{!isCreditCardSelect && CardData != null && (
<div className={containerStyles}>
//CardData is returned by an API call, there are 0 authorable fields
//inside this component -> This allows us to dynamically import it!
<CreditCardDisplay CardData={CardData} />
</div>
)}
I've had 0 issues using Next/Dynamic
to disable Server Side Rendering on my Sitecore JSS components. All the issues come from lazy loading, so as long as you aren't hiding your component behind a conditional render, this will be fine!
Code Example:
import dynamic from 'next/dynamic';
const CustomSitecoreComponent = dynamic(
() => import('child-component/CustomSitecoreComponent'),
{ ssr: false } //Adding the "ssr: false" flag on the import
);
...
{/* THIS SHOULD WORK PERFECTLY FINE */}
<CustomSitecoreComponent fields={props.fields} />
{/* THIS HAS A HIGH CHANCE OF CAUSING AN ISSUE: */}
{!isDisplayCustomComponent && (
<div className={containerStyles}>
<CustomSitecoreComponent fields={props.fields} />
</div>
)}
As of writing, it's very upsetting that I'm unable to use Next/Dynamic
to code split my Sitecore JSS front end; however, we are still able to use Next/Dynamic
in niche use cases, so at least we still have some small benefits. I've submitted a Sitecore support ticket informing them about this issue, so hopefully they will fix/implement the ability to utilize this feature soon!