Step 5 of 10 (50% complete)
The Content Type Registry — Your DI Container
Step Code
The code for this specific step can be found on the following branch:
One of the most deliberate architectural decisions in this starter is where and how content types and React components are registered. Let's look at the file that makes it all work.
The lib/optimizely/init.ts File
This is the single entry point for all registrations. It calls three SDK functions:
import { initContentTypeRegistry, initDisplayTemplateRegistry } from '@optimizely/cms-sdk' import { initReactComponentRegistry } from '@optimizely/cms-sdk/react/server' // 1. Register schemas — tells the SDK what content types exist initContentTypeRegistry([ HeroBlockContentType, CMSPageContentType, FooterContentType, // ... all other types ]) // 2. Register React components — maps content type key to component initReactComponentRegistry({ resolver: { HeroBlock, CMSPage, Footer, // ... all other components }, }) // 3. Register display templates — optional variants of a component initDisplayTemplateRegistry([ProfileBlockDisplayTemplate])
And in app/layout.tsx, there is one import:
import '@/lib/optimizely/init'
That is it. This import runs once on every request and ensures the SDK registry is populated before any page renders.
Why a Separate File?
You might wonder why this does not live directly in app/layout.tsx. The answer is about separation of concerns.
If you placed all these registrations directly in app/layout.tsx, the root layout would import every single component, every content type, and every display template in the application. That component would become an enormous configuration file mixed with JSX — hard to read, hard to maintain, and polluted with concerns that have nothing to do with the layout itself.
By keeping registrations in a dedicated init.ts file, the root layout stays clean:
// app/layout.tsx — clean and focused import '@/lib/optimizely/init' // one line, all registrations handled import './globals.css' export default function RootLayout({ children }) { return <>{children}</> }
The .NET Analogy
If you come from a .NET background, this pattern will feel immediately familiar. Think of it like this:
lib/optimizely/init.tsis yourAddServicescall inStartup.cs— this is where you register everything into the containerapp/layout.tsxis your application entry point — it callsAddServicesonce at startup, then delegates to the frameworkOptimizelyComponentis your dependency injection container — when you ask it to render content, it resolves the correct component from the registry
Just as you would never put builder.Services.AddScoped<IMyService, MyService>() directly in a controller, you do not put content type registrations in your layout component.
The Rule
Every time you add a new content type, there are exactly two places you must update:
initContentTypeRegistry([..., MyNewContentType])— register the schemainitReactComponentRegistry({ resolver: { ..., MyNewComponent } })— register the component
If you add a content type but forget to register the React component, OptimizelyComponent will silently render nothing. If you register the component but forget the content type, the CLI will not push it to CMS. Both registrations are required.
Display Templates
Display templates are an optional but useful feature for creating visual variants of the same content type. For example, the ProfileBlock in this starter has a display template that renders the same content with a different colour scheme:
initiDisplayTemplateRegistry([ProfileBlockDisplayTemplate])
Editors can then choose which display template to use directly in the CMS without needing different content types for what is essentially the same content.
Have questions? I'm here to help!