Sitecore Helix - implementing headless NextJs based site


Headless is really getting attention, separate your actual website rendering from the underlying data structure(s) such as Content Management System (CMS). Sitecore have from my perspective made a sweet-spot where the implementation can be separate from the CMS backend with all the freedom this gives while add the same time give the benefits from the bundled solution with inline WYSIWYG editing. This can be achieved with Sitecore JSS both while hosting your own instance (if it is traditional on-premise/co-located/VM or Kubernetes) or with a pure SaaS solution with Sitecore XM Cloud and head hosted at Vercel or Netlify as the recommended partners from Sitecore.

Sitecore does not recommend to use Helix

As part of XM Cloud Recommended Practices Sitecore states that “Helix” is not needed1

Helix is less useful in front-end projects built with JavaScript frameworks.

❌ Don’t try and force Helix into your front-end JavaScript application.

But if you don’t know it [Helix], learning it is not critical for new XM Cloud projects. Front-end projects are better off following the project structure best practices published by the front-end framework authors.

If your front-end project uses .NET Core, then Helix is still very useful

At the same time it is described how SXA for XM Cloud organize modules accouring to Helix principles and you should follow that for extending SXA2

Structure is still needed

While I really actually like that Sitecore don’t want to force you into a specific structure, we will still need some structure in our solution. The benefits of dividing code base into modules as discussed about Helix in general is still valid.

Luckyli I am not the only here, here is a tweet from Cory House (@housecor)

Ok so let’s agree that we need something, we should not just have a folder called “components” and then everything in here

Helix vs. something else

In the Sitecore community space we have en establish terminology with the Helix architecture, it could have been other names but it really makes sense to me that we use the same language across projects.

Meng Hak have actually made a guide for new NexJS development with Sitecore and touched upon this Sitecore Next.js Guide

As discussed about Helix in general, I find it is important that we group functionality together in some meaningfull groups with the purpose that a developer can relatively easy find functionality in the repository.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
src/components
┣ feature
┃ ┣ BasicContent/
┃ ┃ ┣ Factbox/
┃ ┃ ┃ ┣ Factbox.tsx
┃ ┃ ┃ ┣ Factbox.module.css
┃ ┃ ┣ Slider/
┃ ┃ ┃ ┣ Slider.tsx
┃ ┃ ┃ ┣ Slider.module.css
┃ ┃ ┃ ┣ Slider.graphql
┃ ┣ Navigation/
┃ ┃ ┣ MainNavigation/
┃ ┃ ┃ ┣ MainNavigation.tsx
┃ ┃ ┃ ┣ MainNavigation.module.css
┃ ┃ ┃ ┣ MainNavigation.graphql

In the guide mentioned above, the next grouping is per component, and I do like that anything related to a certain component is kept together both the React implementation, styling, and any GraphQL queries. I would even prefer to have the serialized Sitecore items here also, however I also recognize a structural trade-off here here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
src/
┣ feature
┃ ┣ BasicContent/
┃ ┃ ┣ components/
┃ ┃ ┃ ┣ Factbox.tsx
┃ ┃ ┃ ┣ Slider.tsx
┃ ┃ ┣ styling/
┃ ┃ ┃ ┣ Factbox.module.css
┃ ┃ ┃ ┣ Slider.module.css
┃ ┃ ┣ items/
┃ ┃ ┣ queries/
┃ ┃ ┃ ┣ Slider.graphql

I find this appealing, it encourages code sharing within the feature module (eg. shared styling) and you can easily create new categories within the feature module (eg. services, Typescript types, maybe even a Redux store for the module)

Project layer

Composition of features should be handled in the project layer.

There some specific requirements for the framework, here NextJS, that we have to honor and support, but we can still support our structure.

I find it makes value to split up the logic from happening in the [[...path]].tsx but if the splitting into Project really make sense, you have to consider.

I move Layouts.tsx and NotFound.tsx to project layer, and reference them instead. I create a file for providers so it is clearly visible which providers are used in the project, and use this “component” in the _app.tsx instead of added the providers directly here.

1
2
3
4
5
6
7
8
9
function ProjectProviders({ pageProps, children }: ProvidersProps): JSX.Element {
  return (
    <NextIntlClientProvider messages={pageProps.dictionary} locale={pageProps.locale}>
      <NextAuthProvider>{children}</NextAuthProvider>
    </NextIntlClientProvider>
  );
}

export { ProjectProviders };

“Magic” generation of files

The headless template is using the Sitecore JSS cli and several other scripts to generate some files needed for the solution. One of those is the componentBuilder that creates registration for each component on disk so the componentFactory knows which component to insert based on the component name in the Sitecore JSON rendering.

When moving the components folder, you need to make a little change to the src\rendering\scripts\generate-component-builder\plugins\component-builder.ts file, so the feature folder is watched:

1
2
3
4
5
6
7
8
9
  exec(config: ComponentBuilderPluginConfig) {
    generateComponentBuilder({
      componentRootPath: 'src/feature',
      packages: config.packages,
      watch: config.watch,
    });

    return config;
  }

I have to admit that I am not really happy with those “magic” file generation that hides the couplings in the system that are really important for a developer to understand the code flow. However, I also acknowledge that this is a balance between clarity and avoid doing the same non-valuable tasks consistently.

Similar, for old-school MVC sites we would rarely just generate files with source code, it is very similar to registrations for services container/dependency injection, where we add simple code lines but it explains the code flow. However, we are sometimes using Assembly Scanning which is very similar a way to find “code” and add the necessary registrations.

In some of our solutions we have completely replaced this component builder logic and are just manually creating this file, this allows us to make a clearer separation into features.

src/feature/sitecore-search/index.ts

1
2
3
4
5
6
7
8
import * as SearchResults from './components/SearchResults';
import * as FilteredSearchList from './components/FilteredList/FilteredSearchList';
export * from './lib/search-provider';

export function registerComponents(components: Map<string, unknown>) {
  components.set('SitecoreSearchResults', SearchResults);
  components.set('Filtered Search List', FilteredSearchList);
}

src/feature/index.ts

1
2
3
4
5
import { registerComponents as registerSearch } from './sitecore-search';

export function registerComponents(components: Map<string, unknown>) {
  registerSearch(components);
}

src\project\componentFactory.ts

1
2
3
4
5
6
7
8
import * as sxacomponents from '../foundation/sxastarter';
import * as featurecomponents from '../feature';

const components = new Map();
sxacomponents.registerComponents(components);
featurecomponents.registerComponents(components);

/// the rest of this file is from the scaffolded component factory...

Consider for yourself and your project/team/organization, which approach will make the least confusion and general friction.