If you're not using the components we provide with our base themes at all, this article may not be directly applicable to you.
However, you may want to adopt a similar technique for registering and resolving components in your own custom library, in order to keep your design system flexible.

As background, it is recommended to read Nesting Components Effectively if you haven't done so already.

In projects based on any of our themes, you'll find the file src/components/components-registry.ts. Peek inside it, and you'll find something like this (edited for brevity):

import dynamic from 'next/dynamic';
import { ComponentType } from 'react';

export function getComponent(key: string): ComponentType {
    return components[key];
}

const components = {
    'TextFormControl': dynamic(() => import('./molecules/FormBlock/TextFormControl')),
    'CheckboxFormControl': dynamic(() => import('./molecules/FormBlock/CheckboxFormControl')),
    'ImageBlock': dynamic(() => import('./molecules/ImageBlock')),
    'VideoBlock': dynamic(() => import('./molecules/VideoBlock')),
    'FormBlock': dynamic(() => import('./molecules/FormBlock')),
    'HeroSection': dynamic(() => import('./sections/HeroSection')),
    'TextSection': dynamic(() => import('./sections/TextSection')),
    'ContactSection': dynamic(() => import('./sections/ContactSection')),
    'CtaSection': dynamic(() => import('./sections/CtaSection')),
    'PageLayout': dynamic(() => import('./layouts/PageLayout')),
    'PostLayout': dynamic(() => import('./layouts/PostLayout')),
    // ...and many more
};

All components in this map are "registered": they are mapped from a model name to the actual component, which is the default exported function from the specified module.

By using Next's dynamic import feature, we ensure that the actual import is only performed if and when an entry is actually accessed. Only pages that access components in this map would need to load their source code.

But we're getting ahead of ourselves here. Why is registration needed at all, and in which cases?

When is Registration Needed

Registering your component is needed whenever the rendering code should not include hard-coded component names, but rather dynamically resolve the component to use based on the content type at hand. We use this concept extensively in our themes, as this is what powers the ability to construct flexible pages that are defined by their content.

Here are a few examples for when registration is needed:

  1. For layout-type components, registration is needed since all URLs in a project are typically handled by a single catch-all handler function. Based on the actual content object associated with the specific URL path, and its model name, the actual layout component to use is fetched.
  2. When you're adding a new section-type component, you usually want to make it available for adding under all page layouts which accept sections. These layout do not know of your new section component - but they should have a way to resolve your component based on the content of pages where it's included.
  3. When you're adding a new block-type component that should be nest-able under multiple section types.

How it Works

Let's use an example.

When the general-purpose PageLayout component renders the sections field in the page model, it uses the type property of each section object in the list to get the appropriate rendering component by that section's model name:

export default function PageLayout(props) {
  const sections = props.page.sections || []
  // ...
  {
    sections.map((section, index) => {
      const Component = getComponent(section.type)
      return <Component key={index} {...section} />
    })
  }
  // ...
}

PageLayout does not and should not know the actual component name, but only that there's some component registered for the actual section type encountered. To find out which component that is, the code passes the type property that is available for all content objects and sub-objects to getComponent() which simply looks up that entry in the map (see code above).

Note that since components is a map, you can extend or modify it in various ways:

  1. You can replace the implementation of a component without having to modify the original component's code. Simply create a new component in a new module and change the mapping accordingly.
  2. You can import components that are not in your project's sources but in any of your dependencies - either from 3rd party packages or reusable packages that you develop yourelf.

Modifying and Adding Components in Practice

When you modify a built-in component or add a new component of your own making, there's typically a number of other tasks you would need to perform, such as extending the relevant models and manipulating CSS classes.

To learn more, consult these How To Guides: