In this page you will learn how to create a React component for your content type and how to render it.
Open the src/app/components/Article.tsx file and add the following
import { RichText } from '@episerver/cms-sdk/react/richText';
type Props = {
content: ContentProps<typeof ArticleContentType>;
};
export default function Article({ content }: Props) {
return (
<main>
<h1>{content.heading}</h1>
<RichText content={content.body?.json} />
</main>
);
}Note
For complete documentation on the RichText component including all props, advanced customization options, fallback handling, and TypeScript support, see the RichText Component Reference.
The entire file should look like this:
import { contentType, ContentProps } from '@optimizely/cms-sdk';
import { RichText } from '@optimizely/cms-sdk/react/richText';
export const ArticleContentType = contentType({
key: 'Article',
baseType: '_page',
properties: {
heading: {
type: 'string',
},
body: {
type: 'richText',
},
},
});
type Props = {
content: ContentProps<typeof ArticleContentType>;
};
export default function Article({ content }: Props) {
return (
<main>
<h1>{content.heading}</h1>
<RichText content={content.body?.json} />
</main>
);
}Edit the src/app/layout.tsx to register the Article component. Add the following snippet below initContentTypeRegistry():
initReactComponentRegistry({
resolver: {
Article,
},
});The entire layout.tsx should look like this:
import Article, { ArticleContentType } from '@/components/Article';
import { initContentTypeRegistry } from '@optimizely/cms-sdk';
import { initReactComponentRegistry } from '@optimizely/cms-sdk/react/server';
initContentTypeRegistry([ArticleContentType]);
initReactComponentRegistry({
resolver: {
Article,
},
});
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Open src/app/[...slug]/page.tsx file and replace the last line inside the Page function:
- return <pre>{JSON.stringify(content[0], null, 2)}</pre>
+ return <OptimizelyComponent content={content[0]} />;Your entire file should look like this:
import { GraphClient } from '@optimizely/cms-sdk';
import {
OptimizelyComponent,
withAppContext,
} from '@optimizely/cms-sdk/react/server';
import React from 'react';
type Props = {
params: Promise<{
slug: string[];
}>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};
export async function Page({ params }: Props) {
const { slug } = await params;
const client = new GraphClient(process.env.OPTIMIZELY_GRAPH_SINGLE_KEY!, {
graphUrl: process.env.OPTIMIZELY_GRAPH_GATEWAY,
});
const content = await client.getContentByPath(`/${slug.join('/')}/`);
return <OptimizelyComponent content={content[0]} />;
}
export default withAppContext(Page);Go again to http://localhost:3000/en. You should see your page
The withAppContext HOC wraps your page component to provide request-scoped context:
export async function Page({ params }: Props) {
// ... component logic
}
export default withAppContext(Page);What it does:
Initializes context storage - Sets up isolated, request-scoped storage for context data. This is required when using the context system in React Server Components.
When do you need it:
Since getPreviewContent automatically populates context with preview data, withAppContext is optional for preview pages. Use it when:
- You need context initialized before fetching content (e.g., for manual
setContextDatacalls) - You're using context for non-preview data
- You want to ensure context is available throughout the component tree
Benefits:
- Request isolation - Each request gets its own context storage (critical for server components)
- No prop drilling - Access context data anywhere in your component tree
- Framework-agnostic - Works with any React Server Components framework
Any component can access the context data without props:
import { getContext } from '@optimizely/cms-sdk/react/server';
export function MyComponent() {
const context = getContext();
// Access preview token, locale, etc.
const locale = context?.locale ?? 'en-US';
const isPreview = !!context?.preview_token;
return <div>Locale: {locale}</div>;
}How context is populated:
Context data can be populated in two ways:
- Automatically by
getPreviewContent- This method automatically sets preview_token, locale, key, version, and ctx in the context - Manually via
setContextData()- You can explicitly set context data when needed
This is particularly useful for:
- Displaying locale-specific formatting
- Getting content version and key for manual queries and rendering
You have finished 🎉!
This is the end of the tutorial on how to create your first website using Optimizely CMS SaaS.
You can continue exploring these topics:
- Add Experiences - Learn how to create personalized content experiences for different audiences
- Add Live Preview - Enable real-time content editing and preview capabilities
- Add Display Settings - Configure how your content is displayed across different contexts