(key: K): ContextData[K] | undefined {
+ return this.data[key];
+ }
+
+ clear(): void {
+ this.data = {};
+ }
+ }
+
+ // Define a test content type
+ const TestPageContentType = contentType({
+ key: 'TestPage',
+ baseType: '_page',
+ properties: {
+ title: { type: 'string' },
+ },
+ });
+
+ let mockAdapter: MockAdapter;
+ let client: GraphClient;
+ let originalFetch: typeof global.fetch;
+
+ beforeEach(() => {
+ // Register the test content type
+ initContentTypeRegistry([TestPageContentType]);
+
+ mockAdapter = new MockAdapter();
+ configureAdapter(mockAdapter);
+ initializeRequestContext();
+ client = new GraphClient('test-key');
+
+ // Mock fetch globally
+ originalFetch = global.fetch;
+ global.fetch = vi.fn();
+ });
+
+ afterEach(() => {
+ global.fetch = originalFetch;
+ });
+
+ describe('getPreviewContent', () => {
+ test('should populate global context with preview params', async () => {
+ // Mock successful responses
+ (global.fetch as any).mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ data: {
+ _Content: {
+ item: {
+ _metadata: {
+ types: ['TestPage'],
+ },
+ },
+ },
+ damAssetType: null,
+ },
+ }),
+ });
+
+ (global.fetch as any).mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ data: {
+ _Content: {
+ item: {
+ __typename: 'TestPage',
+ TestPage__title: 'Test',
+ },
+ },
+ },
+ }),
+ });
+
+ const previewParams = {
+ preview_token: 'token-123',
+ key: 'page-key',
+ ctx: 'edit',
+ ver: '1.0',
+ loc: 'en-US',
+ };
+
+ await client.getPreviewContent(previewParams);
+
+ const contextData = getContext();
+ expect(contextData).toEqual({
+ preview_token: 'token-123',
+ locale: 'en-US',
+ key: 'page-key',
+ version: '1.0',
+ type: 'TestPage',
+ ctx: 'edit',
+ });
+ });
+
+ test('should throw error if adapter is broken', async () => {
+ // Configure with a broken adapter that throws
+ const brokenAdapter: ContextAdapter = {
+ initializeContext: () => {
+ throw new Error('Adapter error');
+ },
+ getData: () => {
+ throw new Error('Adapter error');
+ },
+ setData: () => {
+ throw new Error('Adapter error');
+ },
+ set: () => {
+ throw new Error('Adapter error');
+ },
+ get: () => {
+ throw new Error('Adapter error');
+ },
+ clear: () => {
+ throw new Error('Adapter error');
+ },
+ };
+
+ configureAdapter(brokenAdapter);
+ const testClient = new GraphClient('test-key');
+
+ // Mock successful responses
+ (global.fetch as any).mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ data: {
+ _Content: {
+ item: {
+ _metadata: {
+ types: ['TestPage'],
+ },
+ },
+ },
+ damAssetType: null,
+ },
+ }),
+ });
+
+ (global.fetch as any).mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ data: {
+ _Content: {
+ item: {
+ __typename: 'TestPage',
+ TestPage__title: 'Test',
+ },
+ },
+ },
+ }),
+ });
+
+ const previewParams = {
+ preview_token: 'token-123',
+ key: 'page-key',
+ ctx: 'edit',
+ ver: '1.0',
+ loc: 'en-US',
+ };
+
+ // Should throw when trying to populate context with broken adapter
+ await expect(testClient.getPreviewContent(previewParams)).rejects.toThrow(
+ 'Adapter error',
+ );
+ });
+ });
+});
diff --git a/packages/optimizely-cms-sdk/src/graph/__test__/index.test.ts b/packages/optimizely-cms-sdk/src/graph/__test__/index.test.ts
deleted file mode 100644
index a5d3882a..00000000
--- a/packages/optimizely-cms-sdk/src/graph/__test__/index.test.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { describe, expect, test } from 'vitest';
-import { removeTypePrefix } from '../index.js';
-
-describe('removeTypePrefix()', () => {
- test('basic functionality', () => {
- const input = {
- __typename: 'T',
- T__p1: 'p1',
- T__p2: 42,
- T__p3: ['p3', 32],
- T__p4: { nested: 'p4' },
- T__p5: { nested: ['p5'] },
- ShouldBeKept__p6: 'p6',
- };
- const expected = {
- __typename: 'T',
- p1: 'p1',
- p2: 42,
- p3: ['p3', 32],
- p4: { nested: 'p4' },
- p5: { nested: ['p5'] },
- ShouldBeKept__p6: 'p6',
- };
- expect(removeTypePrefix(input)).toStrictEqual(expected);
- });
-
- test('should remove prefixes only in the same level', () => {
- const input = {
- __typename: 'T',
- T__p1: { T_shouldBeKept: 'shouldBeKept' },
- };
- const expected = {
- __typename: 'T',
- p1: { T_shouldBeKept: 'shouldBeKept' },
- };
- expect(removeTypePrefix(input)).toStrictEqual(expected);
- });
-
- test('should work for nested objects', () => {
- const input = {
- __typename: 'T',
- T__p1: {
- __typename: 'U',
- U__p1: 'p1',
- U__p2: {
- __typename: 'V',
- V__p1: 'p1',
- },
- },
- T__p2: [{ __typename: 'U', U__p1: 'p1' }],
- };
- const expected = {
- __typename: 'T',
- p1: {
- __typename: 'U',
- p1: 'p1',
- p2: {
- __typename: 'V',
- p1: 'p1',
- },
- },
- p2: [{ __typename: 'U', p1: 'p1' }],
- };
- expect(removeTypePrefix(input)).toStrictEqual(expected);
- });
-
- test('should not do anything if __typename is not found', () => {
- const input = {
- T__p1: 'hello',
- T__p2: 42,
- T__p3: ['hello', 32],
- T__p4: { nested: 'nested' },
- T__p5: { nested: ['hello'] },
- };
-
- expect(removeTypePrefix(input)).toStrictEqual(input);
- });
-});
diff --git a/packages/optimizely-cms-sdk/src/graph/index.ts b/packages/optimizely-cms-sdk/src/graph/index.ts
index 96e15160..c0687ad7 100644
--- a/packages/optimizely-cms-sdk/src/graph/index.ts
+++ b/packages/optimizely-cms-sdk/src/graph/index.ts
@@ -1,4 +1,3 @@
-import { cache } from 'react';
import {
createSingleContentQuery,
ItemsResponse,
@@ -17,6 +16,7 @@ import {
GraphVariationInput,
localeFilter,
} from './filters.js';
+import { setContext } from '../context/config.js';
/** Options for Graph */
type GraphOptions = {
@@ -468,6 +468,17 @@ export class GraphClient {
{ request: { variables: input, query: GET_CONTENT_METADATA_QUERY } },
);
}
+
+ // Auto-populate context with preview parameters
+ setContext({
+ preview_token: params.preview_token,
+ version: params.ver,
+ locale: params.loc,
+ type: contentTypeName,
+ key: params.key,
+ ctx: params.ctx,
+ });
+
const query = createSingleContentQuery(
contentTypeName,
damEnabled,
diff --git a/packages/optimizely-cms-sdk/src/react/context/__test__/contextWrapper.test.tsx b/packages/optimizely-cms-sdk/src/react/context/__test__/contextWrapper.test.tsx
new file mode 100644
index 00000000..f35bdad1
--- /dev/null
+++ b/packages/optimizely-cms-sdk/src/react/context/__test__/contextWrapper.test.tsx
@@ -0,0 +1,148 @@
+import { describe, expect, test, beforeEach } from 'vitest';
+import { render } from '@testing-library/react';
+import { withAppContext } from '../contextWrapper.js';
+import { getContext } from '../../../context/config.js';
+import ReactContextAdapter from '../../../context/reactContextAdapter.js';
+import { configureAdapter } from '../../../context/config.js';
+
+// Ensure React adapter is configured for tests
+beforeEach(() => {
+ configureAdapter(new ReactContextAdapter());
+});
+
+describe('withAppContext', () => {
+ describe('Basic HOC functionality', () => {
+ test('should wrap component and render it', async () => {
+ const TestComponent = ({ testProp }: { testProp: string }) => (
+ {testProp}
+ );
+
+ const WrappedComponent = withAppContext(TestComponent);
+ const { findByTestId } = render(
+ await WrappedComponent({ testProp: 'test-value' }),
+ );
+
+ const element = await findByTestId('test-component');
+ expect(element).toBeDefined();
+ expect(element.textContent).toBe('test-value');
+ });
+
+ test('should pass through props to wrapped component', async () => {
+ type Props = { name: string; age: number; active: boolean };
+ const TestComponent = ({ name, age, active }: Props) => (
+
+ {name}-{age}-{active.toString()}
+
+ );
+
+ const WrappedComponent = withAppContext(TestComponent);
+ const { container } = render(
+ await WrappedComponent({ name: 'John', age: 30, active: true }),
+ );
+
+ expect(container.textContent).toBe('John-30-true');
+ });
+ });
+
+ describe('Context initialization', () => {
+ test('should initialize empty context', async () => {
+ const TestComponent = () => {
+ const data = getContext();
+ return {JSON.stringify(data)}
;
+ };
+
+ const WrappedComponent = withAppContext(TestComponent);
+ const { findByTestId } = render(await WrappedComponent({}));
+
+ const element = await findByTestId('data');
+ expect(JSON.parse(element.textContent || '{}')).toEqual({});
+ });
+
+ test('should provide fresh context for each wrapped component', async () => {
+ const TestComponent = () => {
+ const data = getContext();
+ return {data?.preview_token || 'empty'}
;
+ };
+
+ const WrappedComponent = withAppContext(TestComponent);
+
+ // First render
+ const { findByTestId: findByTestId1 } = render(
+ await WrappedComponent({}),
+ );
+ const element1 = await findByTestId1('result');
+ expect(element1.textContent).toBe('empty');
+ });
+ });
+
+ describe('Context usage pattern', () => {
+ test('should initialize empty context for components to use', async () => {
+ const TestComponent = () => {
+ const data = getContext();
+ return (
+
+
+ {Object.keys(data || {}).length === 0 ? 'empty' : 'has-data'}
+
+
+ );
+ };
+
+ const WrappedComponent = withAppContext(TestComponent);
+ const { findByTestId } = render(await WrappedComponent({}));
+
+ // withAppContext initializes empty context
+ expect((await findByTestId('is-empty')).textContent).toBe('empty');
+ });
+
+ test('should demonstrate getContext is accessible', async () => {
+ const TestComponent = () => {
+ // Components can call getContext()
+ const data = getContext();
+ return (
+
+ {typeof data === 'object' ? 'object' : 'undefined'}
+
+ );
+ };
+
+ const WrappedComponent = withAppContext(TestComponent);
+ const { findByTestId } = render(await WrappedComponent({}));
+
+ // Context is accessible as an object
+ expect((await findByTestId('context-type')).textContent).toBe('object');
+ });
+ });
+
+ describe('Real-world usage note', () => {
+ test('should document that context is populated by getPreviewContent', async () => {
+ // Note: In actual usage, the workflow is:
+ // 1. withAppContext() initializes empty context storage
+ // 2. getPreviewContent() populates context with preview data
+ // 3. Components access context via getContext()
+ //
+ // React.cache() in React Server Components ensures request-scoped
+ // isolation, but this doesn't work the same way in test environments.
+ // The reactContextAdapter tests verify the adapter functionality directly.
+
+ const InfoComponent = () => {
+ const data = getContext();
+ return (
+
+ {data?.preview_token
+ ? 'preview mode'
+ : 'getPreviewContent not called'}
+
+ );
+ };
+
+ const WrappedComponent = withAppContext(InfoComponent);
+ const { findByTestId } = render(await WrappedComponent({}));
+
+ // Without getPreviewContent being called, no preview data exists
+ expect((await findByTestId('info')).textContent).toBe(
+ 'getPreviewContent not called',
+ );
+ });
+ });
+});
diff --git a/packages/optimizely-cms-sdk/src/react/context/contextWrapper.tsx b/packages/optimizely-cms-sdk/src/react/context/contextWrapper.tsx
new file mode 100644
index 00000000..6e4a13be
--- /dev/null
+++ b/packages/optimizely-cms-sdk/src/react/context/contextWrapper.tsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import {
+ configureAdapter,
+ hasAdapter,
+ initializeRequestContext,
+} from '../../context/config.js';
+import ReactContextAdapter from '../../context/reactContextAdapter.js';
+
+// Configure the React adapter only if no custom adapter has been set
+// This allows users to configure their own adapter before importing from react/server
+if (!hasAdapter()) {
+ configureAdapter(new ReactContextAdapter());
+}
+
+/**
+ * Higher-Order Component that initializes context storage.
+ *
+ * This HOC is designed for React Server Components and uses the configured
+ * context adapter (default: React.cache()) for request-scoped storage.
+ *
+ * NOTE: `getPreviewContent` automatically populates context with preview data.
+ * You may not need this HOC if you're only using that method.
+ *
+ * The HOC is useful for:
+ * - Initializing context before any content fetching
+ * - Ensuring context is available throughout the component tree
+ * - Using context for non-preview data
+ *
+ * Components can access context data using `getContext()`:
+ *
+ * @param Component - The React component to wrap
+ *
+ * @example
+ * ```tsx
+ * import { getContext, setContext } from '@optimizely/cms-sdk/react/server';
+ *
+ * async function MyPage({ params }) {
+ * // Context is initialized by withAppContext
+ * // You can manually set data if needed
+ * setContext({ locale: 'en-US' });
+ *
+ * const context = getContext();
+ * return Locale: {context?.locale}
;
+ * }
+ *
+ * export default withAppContext(MyPage);
+ * ```
+ */
+export function withAppContext(
+ Component: React.ComponentType
,
+) {
+ return async function WrappedWithContext(props: P) {
+ // Initialize context for this request
+ initializeRequestContext();
+
+ return React.createElement(Component, props);
+ };
+}
diff --git a/packages/optimizely-cms-sdk/src/react/richText/lib.ts b/packages/optimizely-cms-sdk/src/react/richText/lib.ts
index 25565a09..59d63728 100644
--- a/packages/optimizely-cms-sdk/src/react/richText/lib.ts
+++ b/packages/optimizely-cms-sdk/src/react/richText/lib.ts
@@ -14,20 +14,20 @@ import {
type TableElement,
type TableCellElement,
} from '../../components/richText/renderer.js';
+import { appendToken } from '../../util/preview.js';
+import { getContextData } from '../../context/config.js';
/**
* React-specific element renderer props (extends shared props with React children)
*/
export interface ElementRendererProps
- extends BaseElementRendererProps,
- PropsWithChildren {}
+ extends BaseElementRendererProps, PropsWithChildren {}
/**
* React-specific props for link elements with type safety
*/
export interface LinkElementProps
- extends Omit,
- PropsWithChildren {
+ extends Omit, PropsWithChildren {
element: LinkElement;
}
@@ -35,8 +35,7 @@ export interface LinkElementProps
* React-specific props for image elements with type safety
*/
export interface ImageElementProps
- extends Omit,
- PropsWithChildren {
+ extends Omit, PropsWithChildren {
element: ImageElement;
}
@@ -44,8 +43,7 @@ export interface ImageElementProps
* React-specific props for table elements with type safety
*/
export interface TableElementProps
- extends Omit,
- PropsWithChildren {
+ extends Omit, PropsWithChildren {
element: TableElement;
}
@@ -53,8 +51,7 @@ export interface TableElementProps
* React-specific props for table cell elements with type safety
*/
export interface TableCellElementRendererProps
- extends Omit,
- PropsWithChildren {
+ extends Omit, PropsWithChildren {
element: TableCellElement;
}
@@ -67,8 +64,7 @@ export type ElementProps = ElementRendererProps;
* React-specific leaf renderer props (extends shared props with React children)
*/
export interface LeafRendererProps
- extends BaseLeafRendererProps,
- PropsWithChildren {}
+ extends BaseLeafRendererProps, PropsWithChildren {}
/**
* Prop type used for custom Leaf components
@@ -120,7 +116,8 @@ export type LeafMap = BaseLeafMap;
* React-specific RichText props
*/
export interface RichTextProps
- extends RichTextPropsBase,
+ extends
+ RichTextPropsBase,
Omit, 'content'> {}
/**
@@ -415,7 +412,7 @@ const HTML_ATTRIBUTE_ELEMENTS = new Set(['table', 'img', 'input', 'canvas']);
*/
export function toReactProps(
attributes: Record,
- elementType?: string
+ elementType?: string,
): Record {
const reactProps: Record = {};
const styleProps: Record = {};
@@ -504,7 +501,7 @@ function parseStyleString(styleString: string): Record {
*/
export function createHtmlComponent(
tag: T,
- config: HtmlComponentConfig = {}
+ config: HtmlComponentConfig = {},
): ElementRenderer {
const Component: ElementRenderer = ({ children, attributes, element }) => {
// Convert to React props and merge with config, passing element type for context
@@ -534,7 +531,7 @@ export function createHtmlComponent(
*/
export function createLinkComponent(
tag: T = 'a' as T,
- config: HtmlComponentConfig = {}
+ config: HtmlComponentConfig = {},
): LinkElementRenderer {
const Component: LinkElementRenderer = ({
children,
@@ -573,7 +570,7 @@ export function createLinkComponent(
*/
export function createImageComponent(
tag: T = 'img' as T,
- config: HtmlComponentConfig = {}
+ config: HtmlComponentConfig = {},
): ImageElementRenderer {
const Component: ImageElementRenderer = ({
children,
@@ -583,9 +580,21 @@ export function createImageComponent(
// Convert to React props and merge with config
const reactProps = toReactProps(attributes || {}, tag as string);
+ // Get preview token from context (React.cache ensures same data per request)
+ let previewToken: string | undefined;
+
+ try {
+ previewToken = getContextData('preview_token');
+ } catch {
+ // If no context adapter is configured, render without a preview token
+ previewToken = undefined;
+ }
+
+ const imageSource = appendToken(element.url, previewToken);
+
// Type-safe access to image properties
const imageProps = {
- src: element.url,
+ src: imageSource,
alt: element.alt,
title: element.title,
width: element.width,
@@ -615,7 +624,7 @@ export function createImageComponent(
*/
export function createTableComponent(
tag: T = 'table' as T,
- config: HtmlComponentConfig = {}
+ config: HtmlComponentConfig = {},
): TableElementRenderer {
const Component: TableElementRenderer = ({
children,
@@ -645,7 +654,7 @@ export function createTableComponent(
*/
export function createTableCellComponent(
tag: T,
- config: HtmlComponentConfig = {}
+ config: HtmlComponentConfig = {},
): TableCellElementRenderer {
const Component: TableCellElementRenderer = ({
children,
@@ -675,7 +684,7 @@ export function createTableCellComponent(
*/
export function createLeafComponent(
tag: T,
- config: HtmlComponentConfig = {}
+ config: HtmlComponentConfig = {},
): LeafRenderer {
const Component: LeafRenderer = ({ children, attributes }) => {
// Convert to React props and merge with config
@@ -707,37 +716,37 @@ export function generateDefaultElements(): ElementMap {
case 'link':
elementMap[type] = createLinkComponent(
'a',
- config.config
+ config.config,
) as ElementRenderer;
break;
case 'image':
elementMap[type] = createImageComponent(
'img',
- config.config
+ config.config,
) as ElementRenderer;
break;
case 'table':
elementMap[type] = createTableComponent(
'table',
- config.config
+ config.config,
) as ElementRenderer;
break;
case 'td':
elementMap[type] = createTableCellComponent(
'td',
- config.config
+ config.config,
) as ElementRenderer;
break;
case 'th':
elementMap[type] = createTableCellComponent(
'th',
- config.config
+ config.config,
) as ElementRenderer;
break;
default:
elementMap[type] = createHtmlComponent(
config.tag as keyof JSX.IntrinsicElements,
- config.config
+ config.config,
);
break;
}
diff --git a/packages/optimizely-cms-sdk/src/react/richText/renderer.ts b/packages/optimizely-cms-sdk/src/react/richText/renderer.ts
index b4a0fdd9..3c64ab39 100644
--- a/packages/optimizely-cms-sdk/src/react/richText/renderer.ts
+++ b/packages/optimizely-cms-sdk/src/react/richText/renderer.ts
@@ -6,7 +6,6 @@ import {
import {
type RenderNode,
type Node,
- type Element,
createElementData,
} from '../../components/richText/renderer.js';
import {
@@ -46,7 +45,7 @@ export class ReactRichTextRenderer extends BaseRichTextRenderer<
Object.entries(config.elements).map(([key, value]) => [
key.toLowerCase(),
value,
- ])
+ ]),
)
: {};
@@ -61,7 +60,7 @@ export class ReactRichTextRenderer extends BaseRichTextRenderer<
Object.entries(config.leafs).map(([key, value]) => [
key.toLowerCase(),
value,
- ])
+ ]),
)
: {};
@@ -85,7 +84,7 @@ export class ReactRichTextRenderer extends BaseRichTextRenderer<
protected createElement(
node: RenderNode,
children: ReactNode[],
- index: number
+ index: number,
): ReactNode {
// Normalize element type to lowercase for consistent lookup
const normalizedElementType = node.elementType!.toLowerCase();
@@ -96,7 +95,7 @@ export class ReactRichTextRenderer extends BaseRichTextRenderer<
const elementData = createElementData(
normalizedElementType,
- node.attributes
+ node.attributes,
);
// Extract text content from render nodes
@@ -113,7 +112,7 @@ export class ReactRichTextRenderer extends BaseRichTextRenderer<
text: textContent,
key: `element-${normalizedElementType}-${index}`, // Unique key for each element
},
- ...children
+ ...children,
);
}
@@ -153,7 +152,7 @@ export class ReactRichTextRenderer extends BaseRichTextRenderer<
text: decodedText,
key: `leaf-${normalizedMark}-${index}-${markIndex}`, // Use normalized mark for key
},
- element
+ element,
);
}
@@ -202,7 +201,7 @@ export class ReactRichTextRenderer extends BaseRichTextRenderer<
* Factory function to create a React renderer
*/
export function createReactRenderer(
- config?: Partial
+ config?: Partial,
): ReactRichTextRenderer {
return new ReactRichTextRenderer(config);
}
diff --git a/packages/optimizely-cms-sdk/src/react/server.tsx b/packages/optimizely-cms-sdk/src/react/server.tsx
index 3a979dec..e4e12151 100644
--- a/packages/optimizely-cms-sdk/src/react/server.tsx
+++ b/packages/optimizely-cms-sdk/src/react/server.tsx
@@ -11,7 +11,6 @@ import {
DisplaySettingsType,
ExperienceCompositionNode,
InferredContentReference,
- ContentProps,
} from '../infer.js';
import { isComponentNode } from '../util/baseTypeUtil.js';
import { parseDisplaySettings } from '../model/displayTemplates.js';
@@ -19,6 +18,17 @@ import { getDisplayTemplateTag } from '../model/displayTemplateRegistry.js';
import { isDev } from '../util/environment.js';
import { appendToken } from '../util/preview.js';
import { OptimizelyReactError } from './error.js';
+export { withAppContext } from './context/contextWrapper.js';
+export {
+ getContext,
+ setContext,
+ getContextData,
+ setContextData,
+ configureAdapter,
+ getAdapter,
+} from '../context/config.js';
+export { ReactContextAdapter } from '../context/reactContextAdapter.js';
+export type { ContextAdapter, ContextData } from '../context/baseContext.js';
type ComponentType = React.ComponentType;
diff --git a/templates/alloy-template/src/app/[...slug]/page.tsx b/templates/alloy-template/src/app/[...slug]/page.tsx
index 8b4fcd14..e54980da 100644
--- a/templates/alloy-template/src/app/[...slug]/page.tsx
+++ b/templates/alloy-template/src/app/[...slug]/page.tsx
@@ -1,7 +1,10 @@
import Footer from '@/components/base/Footer';
import Header from '@/components/base/Header';
import { GraphClient } from '@optimizely/cms-sdk';
-import { OptimizelyComponent } from '@optimizely/cms-sdk/react/server';
+import {
+ OptimizelyComponent,
+ withAppContext,
+} from '@optimizely/cms-sdk/react/server';
import { notFound } from 'next/navigation';
import React from 'react';
import { SidebarNav } from '@/components/base/SidebarNav';
@@ -12,7 +15,7 @@ type Props = {
}>;
};
-export default async function Page({ params }: Props) {
+export async function Page({ params }: Props) {
const { slug } = await params;
const client = new GraphClient(process.env.OPTIMIZELY_GRAPH_SINGLE_KEY!, {
@@ -79,3 +82,5 @@ export default async function Page({ params }: Props) {
>
);
}
+
+export default withAppContext(Page);
diff --git a/templates/alloy-template/src/app/preview/page.tsx b/templates/alloy-template/src/app/preview/page.tsx
index be924b8b..13534fbc 100644
--- a/templates/alloy-template/src/app/preview/page.tsx
+++ b/templates/alloy-template/src/app/preview/page.tsx
@@ -1,5 +1,8 @@
import { GraphClient, type PreviewParams } from '@optimizely/cms-sdk';
-import { OptimizelyComponent } from '@optimizely/cms-sdk/react/server';
+import {
+ OptimizelyComponent,
+ withAppContext,
+} from '@optimizely/cms-sdk/react/server';
import { PreviewComponent } from '@optimizely/cms-sdk/react/client';
import Script from 'next/script';
import Header from '@/components/base/Header';
@@ -10,7 +13,7 @@ type Props = {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};
-export default async function Page({ searchParams }: Props) {
+export async function Page({ searchParams }: Props) {
const client = new GraphClient(process.env.OPTIMIZELY_GRAPH_SINGLE_KEY!, {
graphUrl: process.env.OPTIMIZELY_GRAPH_GATEWAY,
});
@@ -85,3 +88,5 @@ export default async function Page({ searchParams }: Props) {
>
);
}
+
+export default withAppContext(Page);