This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Next.js 16 frontend for Optimizely SaaS CMS using the Content JS SDK (@optimizely/cms-sdk). Deployed to Optimizely Frontend Hosting. Uses the App Router, React 19, Tailwind CSS 4, and TypeScript 5.
npm run dev # Dev server with HTTPS (uses certificates/ dir)
npm run build # Production build
npm run lint # ESLint (flat config, core-web-vitals + typescript)
npm run cms:login # Authenticate with Optimizely CMS CLI
npm run cms:push-config # Push content type definitions to CMS
npm run cms:push-config-force # Force push (overwrites existing types)Deploy via PowerShell: .\deploy.ps1 (requires OPTI_* env vars in .env).
src/optimizely.ts is imported in the root layout and initializes three registries:
- Content Type Registry — all content type definitions from
src/content-types/ - Display Template Registry — visual display options from
src/display-templates/ - React Component Registry — maps content type keys to React components
Every new content type requires registration in all three places plus optimizely.config.mjs.
src/app/[...slug]/page.tsx— Dynamic catch-all that fetches CMS content by URL path viaGraphClient.getContentByPath()and renders with<OptimizelyComponent>src/app/preview/page.tsx— Visual Builder preview route for in-context editingsrc/app/page.tsx— Root/redirects to/en
Each file exports a contentType() call with a CT suffix (e.g., CardBlockCT). Two base types:
_page— Page types (e.g.,ArticlePage)_component— Blocks/elements with composition behaviors:sectionEnabled— Can be placed in Visual Builder sectionselementEnabled— Can be placed as inline elements (restricted property types: no arrays with content, no component/json properties)
Content types are also registered as file paths (strings) in optimizely.config.mjs for the CMS CLI.
Components are organized by their CMS role:
pages/— Page type componentsblocks/— Reusable block componentselements/— Atomic element componentsexperiences/— Visual Builder wrappers (BlankExperience,BlankSection)layout/— Header, Footer, Logo
Barrel exports in src/components/index.ts must match content type keys used in optimizely.ts resolver.
import { Infer } from '@optimizely/cms-sdk';
import { getPreviewUtils } from '@optimizely/cms-sdk/react/server';
type Props = {
opti: Infer<typeof SomeContentTypeCT>;
displaySettings?: Infer<typeof SomeDisplayTemplate>;
};
export default function MyComponent({ opti, displaySettings }: Props) {
const { pa, src } = getPreviewUtils(opti);
// pa('propertyName') — adds preview attributes for Visual Builder editing
// src(opti.image) — returns optimized image URL from CMS CDN
return <div {...pa('title')}>{opti.title}</div>;
}Display templates (src/display-templates/) define visual styling options (alignment, heading level) shown in the CMS sidebar. Semantic data belongs in content type properties, not display templates.
- Create
src/content-types/NewType.ts— define withcontentType(), export asNewTypeCT - Export from
src/content-types/index.ts - Create component in
src/components/{pages,blocks,elements}/NewType.tsx - Export from
src/components/index.ts - Add to resolver map in
src/optimizely.ts - Add file path to
optimizely.config.mjscomponents array - Run
npm run cms:push-configto sync to CMS
Copy .env.template to .env. Key variables:
OPTIMIZELY_GRAPH_SINGLE_KEY— Required for content fetchingOPTIMIZELY_CMS_URL— CMS instance URLOPTIMIZELY_GRAPH_GATEWAY— Graph endpoint (defaults tohttps://cg.optimizely.com); handled bysrc/lib/config.tsgetGraphGatewayUrl()which accounts for differences between local dev and Frontend Hosting runtime
@/* maps to ./src/* (configured in tsconfig.json).
Remote images allowed from *.cms.optimizely.com and cdn.optimizely.com (configured in next.config.ts).