Working with placeholders in a JSS Next.js app

Abstract

Placeholder techniques for Next.js JSS applications

There are several ways to use placeholders in a JSS Next.js app.

Tip

We recommend you read our guide for placeholders in JSS apps to understand what placeholders are and how they work.

The most common way to add a placeholder to a component is to use the Placeholder component.

import { Placeholder } from '@sitecore-jss/sitecore-jss-nextjs';

const App = ({ rendering }) => (
  <div>
    <h1>My App</h1>
    <Placeholder name="jss-main" rendering={rendering} />
  </div>
);

For the Placeholder component:

  • The name is the key to the placeholder you are exposing.

  • The rendering can represent:

    • The current Sitecore-provided layout/route data.

    • If you are exposing a placeholder from within another component, parent component data.

The higher-order component (HOC) pattern lets you get an array of the components in a placeholder injected as a property to the component that exposes the placeholder.

import { withPlaceholder } from '@sitecore-jss/sitecore-jss-nextjs';

const Tabs = ({ tabs }) => {
  return (
    <div>
      {tabs}
    </div>
  );
};

export default withPlaceholder('tabs')(Tabs);

When you emit the component array, the application renders the components where you emit them.

If you emit the placeholder with the HOC technique, the placeholder has no wrapping component and renders inline. The HOC approach is useful when you are using libraries based on a specific component hierarchy. If you use the basic placeholder technique, all child components render underneath it in the component tree.

For example, if you want to use the react-fullpage library with the following structure:

<SectionsContainer>
  <Section color="#69D2E7">Page 1</Section>
  <Section color="#A7DBD8">Page 2</Section>
  <Section color="#E0E4CC">Page 3</Section>
</SectionsContainer>

The library expects the following component hierarchy:

SectionsContainer
    Section

However, if you add the Section components using the basic placeholder technique:

<SectionsContainer>
  <Placeholder name="jss-sections" rendering={rendering} />
</SectionsContainer>

the resulting component hierarchy is:

SectionsContainer
    Placeholder    
        YourComponent 
            Section

With the HOC-based placeholder, you can use inline components or component transformation to achieve a flatter component hierarchy.

Inline components

If the library does not prevent a single layer of component wrapping, to obtain a component hierarchy such as:

SectionsContainer
    YourComponent 
        Section

you can place the child component into your rendering component. For example:

import { withPlaceholder } from '@sitecore-jss/sitecore-jss-nextjs';

const ContainerComponent = ({ sectionsPlaceholder }) => {
  return <SectionsContainer>{sectionsPlaceholder}</SectionsContainer>;
};

// you can also alias the property for the placeholder when using the HOC - or pass an array of placeholders
export const ContainerSitecoreComponent = withPlaceholder({ placeholder: 'sections', prop: 'sectionsPlaceholder' })(ContainerComponent);

// the <Section> is in the child Sitecore component added to the placeholder
export const ChildSitecoreComponent = (props) => (
  <Section>
    <h1>Your components here</h1>
  </Section>
);

Component transformation

To achieve a completely flat component hierarchy, such as:

SectionsContainer
    Section

you can take advantage of the injected property being an array and transform the child components with a wrapper using the map function.

The child Sitecore components can be completely unaware of the wrapping and render only their content for a clean separation of concerns when using this technique.

For example:

const ContainerComponent = ({ sectionsPlaceholder }) => {
  return (
    <SectionsContainer>
      {sectionsPlaceholder.map((component, index) => {
        // this if is important, as it prevents breakage when using Sitecore Experience Editor
        if (component.props && component.props.type === 'text/sitecore') return component;

        // wraps _all_ child components in a <Section> component
        return <Section key={index}>{component}</Section>;
      })}
    </SectionsContainer>
  );
};

export const ContainerSitecoreComponent = withPlaceholder({ placeholder: 'sections', prop: 'sectionsPlaceholder' })(ContainerComponent);

JSS supports the render props pattern as an alternative to higher-order components. Using the render property of the <Placeholder> component, you can take over the rendering of placeholder contents the same way as with the higher-order component and use dynamic properties.

The following example illustrates how to get the components array and render it using render property.

import { Placeholder } from '@sitecore-jss/sitecore-jss-nextjs';

const App = ({ rendering }) => (
  <div>
    <h1>My App</h1>
    <Placeholder name="jss-main" rendering={rendering} render={(components, placeholderData, props) => <div>{components}</div>} />
  </div>
);

You can use two optional arguments to the render function of the placeholder:

  • The props parameter is a mirror of the properties passed to the <Placeholder>.

  • The placeholderData parameter provides the current placeholder's layout data.

You can access the data of a placeholder instead of its components, for example, for components such as tabs that you want to render in two places (the tab title and the tab contents). Because the placeholder hierarchy is a big JavaScript object, you can traverse it using props.rendering to discover child component data and fields.

const Tabs = ({ rendering, tabs }) => (
<Tab.Container>
  <div class="tabsHeading">
    {(rendering.placeholders.tabs || [])
      .filter((tab) => tab && tab.fields && tab.fields.title)
      .map((tab, index) => (
        <Tab.Heading key={index}>
          <Text field={tab.fields.title} />
        </Tab.Heading>
      ))}
  </div>

  <div class="tabContents">
    {tabs}
  </div>
</Tab.Container>
);

export default withPlaceholder('tabs')(Tabs);

Placeholders provide options for enhancing the developer/editor experience by using custom components. If you use the basic placeholder technique, you can configure which custom components the placeholder renders for errors or missing JavaScript implementations with the help of the Placeholder component properties. If you use the HOC pattern, you can use the propsTransformer option to inject the same properties using a function.

Error components

If a rendering error occurs in a placeholder, the placeholder displays an error component instead of the placeholder contents and logs the error details to the console. You can customize this component by substituting a custom component using the errorComponent prop.

Missing Component components

If a placeholder contains a rendering name unknown to the componentFactory (for example, a back-end developer creates a Foo rendering and adds it to a page, but there is no Foo.js yet), the rendering is replaced with a MissingComponent component defined in the missingComponentComponent property of your component. The default implementation is a simple message, but you can customize it by using a custom component on the missingComponentComponent property.