From 33e7b4982c57d5fb15c503f20aefb774ed5eecf5 Mon Sep 17 00:00:00 2001 From: Carlos Saito Date: Tue, 4 Nov 2025 18:08:45 +0100 Subject: [PATCH 1/8] Allow definitions without default --- packages/optimizely-cms-sdk/src/render/componentRegistry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/optimizely-cms-sdk/src/render/componentRegistry.ts b/packages/optimizely-cms-sdk/src/render/componentRegistry.ts index 15b06216..dff1cb98 100644 --- a/packages/optimizely-cms-sdk/src/render/componentRegistry.ts +++ b/packages/optimizely-cms-sdk/src/render/componentRegistry.ts @@ -14,7 +14,7 @@ */ type ComponentWithVariants = { /** Default component */ - default: C; + default?: C; /** * Tagged variants, where the keys are tag names and the values are the corresponding components. From aa797345c6a56f996294581016f4f61fe1db62a3 Mon Sep 17 00:00:00 2001 From: Carlos Saito Date: Tue, 4 Nov 2025 18:09:22 +0100 Subject: [PATCH 2/8] Add new test page --- .../src/app/only-tags/[...slug]/page.tsx | 66 ++++++++++++++++++ .../test-website/src/app/only-tags/layout.tsx | 68 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 __test__/test-website/src/app/only-tags/[...slug]/page.tsx create mode 100644 __test__/test-website/src/app/only-tags/layout.tsx diff --git a/__test__/test-website/src/app/only-tags/[...slug]/page.tsx b/__test__/test-website/src/app/only-tags/[...slug]/page.tsx new file mode 100644 index 00000000..3ddc8dee --- /dev/null +++ b/__test__/test-website/src/app/only-tags/[...slug]/page.tsx @@ -0,0 +1,66 @@ +import { GraphClient, GraphErrors } from '@optimizely/cms-sdk'; +import { OptimizelyComponent } from '@optimizely/cms-sdk/react/server'; +import React from 'react'; + +type Props = { + params: Promise<{ + slug: string[]; + }>; + // Assume that search params are correct: + searchParams: Promise<{ variation?: string }>; +}; + +function handleGraphErrors(err: unknown): never { + if (err instanceof GraphErrors.GraphResponseError) { + console.log('Error message:', err.message); + console.log('Query:', err.request.query); + console.log('Variables:', err.request.variables); + } + if (err instanceof GraphErrors.GraphContentResponseError) { + console.log('Detailed errors: ', err.errors); + } + + throw err; +} + +function getRandomElement(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]; +} + +export default async function Page({ params, searchParams }: Props) { + const { slug } = await params; + const path = `/en/${slug.join('/')}/`; + + const client = new GraphClient(process.env.OPTIMIZELY_GRAPH_SINGLE_KEY!, { + graphUrl: process.env.OPTIMIZELY_GRAPH_URL, + }); + + const variation = (await searchParams).variation; + + if (variation) { + const list = await client + .getContentByPath(path, { + variation: { include: 'SOME', value: [variation] }, + }) + .catch(handleGraphErrors); + return ; + } + + const items = await client + .getContentByPath(path, { + variation: { include: 'SOME', value: [], includeOriginal: true }, + }) + .catch(handleGraphErrors); + + console.log('Variations', items.length); + + const content = getRandomElement(items); + + return ( + <> +

Number of variations: {items.length}

+
+ + + ); +} diff --git a/__test__/test-website/src/app/only-tags/layout.tsx b/__test__/test-website/src/app/only-tags/layout.tsx new file mode 100644 index 00000000..78765c58 --- /dev/null +++ b/__test__/test-website/src/app/only-tags/layout.tsx @@ -0,0 +1,68 @@ +import { + BlankSection, + BlankSection2, + Column, + ColumnA, + Component1, + Component1A, + Component1B, + Component2, + Component2A, + Component3, + Component3C, + ct1, + ct2, + ct3, + dt1, + dt2, + dt3, + dt4, + dt5, + dt6, + Row, + RowA, +} from '@/components/with-display-templates'; +import { + initContentTypeRegistry, + initDisplayTemplateRegistry, +} from '@optimizely/cms-sdk'; +import { initReactComponentRegistry } from '@optimizely/cms-sdk/react/server'; + +initContentTypeRegistry([ct1, ct2, ct3]); +initDisplayTemplateRegistry([dt1, dt2, dt3, dt4, dt5, dt6]); +initReactComponentRegistry({ + // In this example, all content types have components but only with tags + resolver: { + test_c1: { + tags: { + tagA: Component1A, + tagB: Component1B, + }, + }, + 'test_c2:tagA': Component2A, + test_c3: { + tags: { + tagC: Component3C, + }, + }, + 'BlankSection:tagA': BlankSection2, + _Row: { + tags: { + tagA: RowA, + }, + }, + '_Column:tagA': ColumnA, + }, +}); + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} From 724fa4c7f3ca7b973621d1d0e9b103ed3d45aa73 Mon Sep 17 00:00:00 2001 From: Carlos Saito Date: Tue, 4 Nov 2025 18:10:02 +0100 Subject: [PATCH 3/8] Re-shape getComponent function --- .../src/render/componentRegistry.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/optimizely-cms-sdk/src/render/componentRegistry.ts b/packages/optimizely-cms-sdk/src/render/componentRegistry.ts index dff1cb98..e6834abc 100644 --- a/packages/optimizely-cms-sdk/src/render/componentRegistry.ts +++ b/packages/optimizely-cms-sdk/src/render/componentRegistry.ts @@ -53,7 +53,7 @@ function hasVariants(obj: unknown): obj is ComponentWithVariants { } /** Returns the default component in an {@linkcode ComponentEntry} */ -function getDefaultComponent(entry: ComponentEntry): C { +function getDefaultComponent(entry: ComponentEntry): C | undefined { if (hasVariants(entry)) { return entry.default; } else { @@ -100,21 +100,23 @@ export class ComponentRegistry { const entry = this.resolver[contentType]; - if (!entry) { - return undefined; - } - if (!options.tag) { + if (!entry) { + return undefined; + } return getDefaultComponent(entry); } - // Search for the component `${contentType}:${tag}` const taggedEntry = this.resolver[`${contentType}:${options.tag}`]; if (taggedEntry) { return getDefaultComponent(taggedEntry); } + if (!entry) { + return undefined; + } + return ( // Search for the component with the tag in the component definition getTagComponent(entry, options.tag) ?? From 21c5850b45913e096cc563569d5d97229077410f Mon Sep 17 00:00:00 2001 From: Carlos Saito Date: Tue, 4 Nov 2025 18:12:22 +0100 Subject: [PATCH 4/8] Remove page --- .../src/app/only-tags/[...slug]/page.tsx | 66 ------------------ .../test-website/src/app/only-tags/layout.tsx | 68 ------------------- 2 files changed, 134 deletions(-) delete mode 100644 __test__/test-website/src/app/only-tags/[...slug]/page.tsx delete mode 100644 __test__/test-website/src/app/only-tags/layout.tsx diff --git a/__test__/test-website/src/app/only-tags/[...slug]/page.tsx b/__test__/test-website/src/app/only-tags/[...slug]/page.tsx deleted file mode 100644 index 3ddc8dee..00000000 --- a/__test__/test-website/src/app/only-tags/[...slug]/page.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { GraphClient, GraphErrors } from '@optimizely/cms-sdk'; -import { OptimizelyComponent } from '@optimizely/cms-sdk/react/server'; -import React from 'react'; - -type Props = { - params: Promise<{ - slug: string[]; - }>; - // Assume that search params are correct: - searchParams: Promise<{ variation?: string }>; -}; - -function handleGraphErrors(err: unknown): never { - if (err instanceof GraphErrors.GraphResponseError) { - console.log('Error message:', err.message); - console.log('Query:', err.request.query); - console.log('Variables:', err.request.variables); - } - if (err instanceof GraphErrors.GraphContentResponseError) { - console.log('Detailed errors: ', err.errors); - } - - throw err; -} - -function getRandomElement(arr: T[]): T { - return arr[Math.floor(Math.random() * arr.length)]; -} - -export default async function Page({ params, searchParams }: Props) { - const { slug } = await params; - const path = `/en/${slug.join('/')}/`; - - const client = new GraphClient(process.env.OPTIMIZELY_GRAPH_SINGLE_KEY!, { - graphUrl: process.env.OPTIMIZELY_GRAPH_URL, - }); - - const variation = (await searchParams).variation; - - if (variation) { - const list = await client - .getContentByPath(path, { - variation: { include: 'SOME', value: [variation] }, - }) - .catch(handleGraphErrors); - return ; - } - - const items = await client - .getContentByPath(path, { - variation: { include: 'SOME', value: [], includeOriginal: true }, - }) - .catch(handleGraphErrors); - - console.log('Variations', items.length); - - const content = getRandomElement(items); - - return ( - <> -

Number of variations: {items.length}

-
- - - ); -} diff --git a/__test__/test-website/src/app/only-tags/layout.tsx b/__test__/test-website/src/app/only-tags/layout.tsx deleted file mode 100644 index 78765c58..00000000 --- a/__test__/test-website/src/app/only-tags/layout.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { - BlankSection, - BlankSection2, - Column, - ColumnA, - Component1, - Component1A, - Component1B, - Component2, - Component2A, - Component3, - Component3C, - ct1, - ct2, - ct3, - dt1, - dt2, - dt3, - dt4, - dt5, - dt6, - Row, - RowA, -} from '@/components/with-display-templates'; -import { - initContentTypeRegistry, - initDisplayTemplateRegistry, -} from '@optimizely/cms-sdk'; -import { initReactComponentRegistry } from '@optimizely/cms-sdk/react/server'; - -initContentTypeRegistry([ct1, ct2, ct3]); -initDisplayTemplateRegistry([dt1, dt2, dt3, dt4, dt5, dt6]); -initReactComponentRegistry({ - // In this example, all content types have components but only with tags - resolver: { - test_c1: { - tags: { - tagA: Component1A, - tagB: Component1B, - }, - }, - 'test_c2:tagA': Component2A, - test_c3: { - tags: { - tagC: Component3C, - }, - }, - 'BlankSection:tagA': BlankSection2, - _Row: { - tags: { - tagA: RowA, - }, - }, - '_Column:tagA': ColumnA, - }, -}); - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); -} From 9baffaa8311a66e0fd28fa427fbf5b63344890f9 Mon Sep 17 00:00:00 2001 From: Carlos Saito Date: Tue, 4 Nov 2025 18:47:02 +0100 Subject: [PATCH 5/8] Define components only with tags --- __test__/test-website/src/app/en/layout.tsx | 15 ++++++++++++- .../src/components/with-display-templates.tsx | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/__test__/test-website/src/app/en/layout.tsx b/__test__/test-website/src/app/en/layout.tsx index 4cf81d41..37a45597 100644 --- a/__test__/test-website/src/app/en/layout.tsx +++ b/__test__/test-website/src/app/en/layout.tsx @@ -10,9 +10,13 @@ import { Component2A, Component3, Component3C, + Component6, + Component7, ct1, ct2, ct3, + ct6, + ct7, dt1, dt2, dt3, @@ -28,7 +32,7 @@ import { } from '@optimizely/cms-sdk'; import { initReactComponentRegistry } from '@optimizely/cms-sdk/react/server'; -initContentTypeRegistry([ct1, ct2, ct3]); +initContentTypeRegistry([ct1, ct2, ct3, ct6, ct7]); initDisplayTemplateRegistry([dt1, dt2, dt3, dt4, dt5, dt6]); initReactComponentRegistry({ resolver: { @@ -57,6 +61,15 @@ initReactComponentRegistry({ }, _Column: Column, '_Column:tagA': ColumnA, + + // Content type "test_ct6" only has a component with tag + 'test_ct6:tagA': Component6, + test_c7: { + // default: Component7, + tags: { + tagA: Component7, + }, + }, }, }); diff --git a/__test__/test-website/src/components/with-display-templates.tsx b/__test__/test-website/src/components/with-display-templates.tsx index c36c3619..7197be44 100644 --- a/__test__/test-website/src/components/with-display-templates.tsx +++ b/__test__/test-website/src/components/with-display-templates.tsx @@ -33,6 +33,18 @@ export const ct3 = contentType({ baseType: '_experience', }); +export const ct6 = contentType({ + key: 'test_ct6', + baseType: '_component', + compositionBehaviors: ['elementEnabled', 'sectionEnabled'], +}); + +export const ct7 = contentType({ + key: 'test_c7', + baseType: '_component', + compositionBehaviors: ['elementEnabled', 'sectionEnabled'], +}); + export const dt1 = displayTemplate({ key: 'test_dt1', baseType: '_component', @@ -90,6 +102,8 @@ export const dt6 = displayTemplate({ type Props1 = { opti: Infer }; type Props2 = { opti: Infer }; type Props3 = { opti: Infer }; +type Props6 = { opti: Infer }; +type Props7 = { opti: Infer }; type BlankSectionProps = { opti: Infer; @@ -124,6 +138,13 @@ export function Component3({ opti }: Props3) { ); } +export function Component6({}: Props6) { + return
This is Component6
; +} + +export function Component7({}: Props6) { + return
This is Component7
; +} export function Component3C({ opti }: Props3) { return (
From 7dbb37c25931948e1c939fe52f050c84e143fb9c Mon Sep 17 00:00:00 2001 From: Carlos Saito Date: Tue, 4 Nov 2025 18:47:38 +0100 Subject: [PATCH 6/8] Accepts obj without defaults --- packages/optimizely-cms-sdk/src/render/componentRegistry.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/optimizely-cms-sdk/src/render/componentRegistry.ts b/packages/optimizely-cms-sdk/src/render/componentRegistry.ts index e6834abc..61b0cca8 100644 --- a/packages/optimizely-cms-sdk/src/render/componentRegistry.ts +++ b/packages/optimizely-cms-sdk/src/render/componentRegistry.ts @@ -47,9 +47,7 @@ type ComponentMap = Record>; /** Returns true if `obj` is type {@linkcode ComponentWithVariants} */ function hasVariants(obj: unknown): obj is ComponentWithVariants { - return ( - typeof obj === 'object' && obj !== null && 'default' in obj && 'tags' in obj - ); + return typeof obj === 'object' && obj !== null && 'tags' in obj; } /** Returns the default component in an {@linkcode ComponentEntry} */ From ee1dbbba9350d3cbb62605a119f7e78fb0c62b06 Mon Sep 17 00:00:00 2001 From: Carlos Saito Date: Wed, 5 Nov 2025 13:07:38 +0100 Subject: [PATCH 7/8] Update __test__/test-website/src/components/with-display-templates.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- __test__/test-website/src/components/with-display-templates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__test__/test-website/src/components/with-display-templates.tsx b/__test__/test-website/src/components/with-display-templates.tsx index 7197be44..df80c833 100644 --- a/__test__/test-website/src/components/with-display-templates.tsx +++ b/__test__/test-website/src/components/with-display-templates.tsx @@ -142,7 +142,7 @@ export function Component6({}: Props6) { return
This is Component6
; } -export function Component7({}: Props6) { +export function Component7({}: Props7) { return
This is Component7
; } export function Component3C({ opti }: Props3) { From f5a72dc61b2a617045ad613447ff9385f5f81ff0 Mon Sep 17 00:00:00 2001 From: Carlos Saito Date: Wed, 5 Nov 2025 13:08:28 +0100 Subject: [PATCH 8/8] Fix inconsistency --- __test__/test-website/src/app/en/layout.tsx | 2 +- __test__/test-website/src/components/with-display-templates.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/__test__/test-website/src/app/en/layout.tsx b/__test__/test-website/src/app/en/layout.tsx index 37a45597..3a7176cc 100644 --- a/__test__/test-website/src/app/en/layout.tsx +++ b/__test__/test-website/src/app/en/layout.tsx @@ -63,7 +63,7 @@ initReactComponentRegistry({ '_Column:tagA': ColumnA, // Content type "test_ct6" only has a component with tag - 'test_ct6:tagA': Component6, + 'test_c6:tagA': Component6, test_c7: { // default: Component7, tags: { diff --git a/__test__/test-website/src/components/with-display-templates.tsx b/__test__/test-website/src/components/with-display-templates.tsx index 7197be44..28242e7a 100644 --- a/__test__/test-website/src/components/with-display-templates.tsx +++ b/__test__/test-website/src/components/with-display-templates.tsx @@ -34,7 +34,7 @@ export const ct3 = contentType({ }); export const ct6 = contentType({ - key: 'test_ct6', + key: 'test_c6', baseType: '_component', compositionBehaviors: ['elementEnabled', 'sectionEnabled'], });