Sitecore Composable Components 101

Anton Tishchenko
Anton Tishchenko
Cover Image for Sitecore Composable Components 101

Composable, composable, composable…. We hear this word too often. Many platforms advertise itself as composable: Sitecore, ContentStack, Contentful, Drupal, Optimizely, etc. And they are really composable. But do you get the composable solution at the finish of the project? How tightly coupled are parts of your system after implementation is finished? Let’s figure it out.

Firstly, let’s define “composable” term to be on the same page. Composable architecture is an approach of building software systems with flexible, reusable components. We can simplify this definition for our case. We have two parts CMS and frontend. We should be able to replace any part in this equation. There is no problem with frontend replacement. We just need to write new components. But we can’t easily replace CMS. There is no common standard for API. Some changes still will be required. Let’s define that composable implementation of frontend is the implementation when you can change your CMS without any changes in the code of frontend components, but with some small changes in project. In other words, our frontend components should be able to work with 2,3,… content management systems at once.

Does frontend components in your implementation are composable?

As example we can take a random component from Sitecore “PLAY! Summit” demo. (Yes, I know that this demo has completely different purpose. We just need a some example. And these demo components looks very similar to real world implementations)

TextCta.tsx:

import { Text, Field, withDatasourceCheck } from '@sitecore-jss/sitecore-jss-nextjs';
import { ComponentProps } from 'lib/component-props';

type TextCtaProps = ComponentProps & {
  fields: {
    title: Field<string>;
    subTitle: Field<string>;
  };
};

const TextCta = (props: TextCtaProps): JSX.Element => {
  const sxaStyles = `${props.params?.styles || ''}`;

  return (
    <section className={`text-cta ${sxaStyles}`}>
      <div className="container cta-content">
        <Text tag="h2" className="cta-title" field={props.fields.title} />
        <Text tag="p" className="cta-subtitle" field={props.fields.subTitle} />
      </div>
    </section>
  );
};

export const Default = withDatasourceCheck()<TextCtaProps>(TextCta);

Can this component be used with another CMS? No, it can’t. The reason is usage of Sitecore SDK inside the component. We see Text, Field, withDatasourceCheck parts that we can’t use with other CMS. Yes, we can rewrite this component for other CMS usage. But rewriting means that it is not composable.

Composable architecture

There is a way to write a composable frontend. That is not something newly invented, we will just adopt well-known software development design principle: Separation of concerns.

The main idea will be taking an existing component and separation it into two components. The first part will be “Sitecore” aware component. It will be dependent on Sitecore JSS SDK. The second part will be “clean” component. It will work with basic types and doesn’t know anything about Sitecore at all.

Let’s take another basic Sitecore styleguide component

ContentBlock.tsx:

import { Text, RichText, Field, withDatasourceCheck } from '@sitecore-jss/sitecore-jss-nextjs';
import { ComponentProps } from 'lib/component-props';

type ContentBlockProps = ComponentProps & {
  fields: {
    heading: Field<string>;
    content: Field<string>;
  };
};

const ContentBlock = ({ fields }: ContentBlockProps): JSX.Element => (
  <div className="contentBlock">
    <Text tag="h2" className="contentTitle" field={fields.heading} />
    <RichText className="contentDescription" field={fields.content} />
  </div>
);

export default withDatasourceCheck()<ContentBlockProps>(ContentBlock);

We can split this component into two separate components.

ContentBlock.Sitecore.tsx:

import { Text, RichText, Field, withDatasourceCheck } from '@sitecore-jss/sitecore-jss-nextjs';
import { ComponentProps } from 'lib/component-props';
import ContentBlock from './ContentBlock';

type ContentBlockSitecoreProps = ComponentProps & {
  fields: {
    heading: Field<string>;
    content: Field<string>;
  };
};

const ContentBlockSitecore = ({ fields }: ContentBlockSitecoreProps): JSX.Element => (
  <ContentBlock
    heading={<Text field={fields.heading} />}
    content={<RichText field={fields.content} />}
  />
);

export default withDatasourceCheck()<ContentBlockSitecoreProps>(ContentBlockSitecore);

ContentBlock.tsx:

import { ReactNode } from 'react';

type ContentBlockProps = {
  heading: string | ReactNode;
  content: string | ReactNode;
};

const ContentBlock = ({ heading, content }: ContentBlockProps): JSX.Element => (
  <div className="contentBlock">
    {heading}
    {content}
  </div>
);
export default ContentBlock;

composable

We get more code, but cleaner and composable architecture. Sitecore component plays the role of wrapper. It takes Sitecore specific fields and passes them to “clean” component. “Clean” component doesn’t know anything about Sitecore. It just takes renders passed props. If we need to use this component with another CMS, we will need to add another wrapper. We will not need to change “clean” component at all. The risk of breaking something will be minimal.

Costs

Separation of concerns on components level will require additional effort. You will get more code and more files. The implemented solution will be more expensive at short perspective. However, if you use Storybook then you may start to write stories for your “clean” components. That will make your stories cleaner and easier to support. More code in components, less code in stories. But the main benefit will be ability to reuse your component in any other non-Sitecore project.

Conclusion

If “composable” is not just a buzz word for your project then you need to separate concerns on frontend component level.

P.S. This article contains examples for React (Next.js) framework. The similar approach could be used for Vue and Angular. We don’t add examples for them as they take only 25% of Sitecore JSS market and share is decreasing.