### Usage example for opti-fx pull command Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/fx-cli-commands.md Shows the initial setup and subsequent sync commands for the `opti-fx` CLI. The setup command requires `--token`, `--project`, and `--key` environment variables. The `pull` command syncs flags, and `--force` removes code-only flags and overwrites type changes. ```bash # Initial setup in the app folder cd apps/frontend yarn opti-fx setup --token $OPTIMIZELY_FX_PAT --project $OPTIMIZELY_FX_PROJECT --key $OPTIMIZELY_FX_SDKKEY # Keep flags in sync after changing flag configuration in FX yarn opti-fx pull # Sync while forcing remote truth (removes code-only flags; overwrites type changes) yarn opti-fx pull --force ``` -------------------------------- ### SetupCommand CLI module for 'setup' command Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/fx-cli-commands.md Defines the 'setup' CLI command for initializing Feature Experimentation on Vercel. The handler is abbreviated with '...' in the source; the full flow is described in the surrounding documentation. ```ts export const SetupCommand : CliModule = { command: "setup", describe: "Run the initial setup of Feature Experimentation for Next.JS (App Router) on Vercel", async handler(args, opts) { ... } } ``` -------------------------------- ### Usage Examples Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/lib-urls-conversions.md Examples of using URL conversion and prop filtering utilities in components. ```APIDOC ## Usage Examples ### Building href URLs from a CMS reference ```tsx import { refToURL } from "@/lib/conversions" import { getLinkData, linkDataToUrl } from "@/lib/urls" export function articleLink(reference: any) { const absolute = refToURL(reference) // URL | null const viaLinkData = linkDataToUrl(getLinkData({ url: reference })) // URL | undefined return absolute?.href ?? viaLinkData?.href ?? "#" } ``` ### Filtering CMS framework props before rendering a plain element ```tsx import { omitCmsComponentProps } from "@/lib/filter-props" export function cleanProps(props: any) { return omitCmsComponentProps(props) // { contentLink, ctx, data, editProps, inEditMode, layoutProps } removed } ``` ``` -------------------------------- ### Run the development server Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/apps/frontend/README.md Use one of these commands to start the Next.js development server. The page auto-updates as you edit app/page.tsx. ```bash npm run dev # or yarn dev # or pnpm dev ``` -------------------------------- ### Manual invocation example Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/cloudflare-loader.md Shows how to call the loader manually with sample parameters and the resulting Cloudflare Images URL. ```javascript import cloudflareLoader from "./src/cloudflareLoader"; const optimized = cloudflareLoader({ src: "https://example.cms.optimizely.com/globalassets/photo.jpg", width: 640, height: 480, quality: 80, }); // -> https://example.cms.optimizely.com/cdn-cgi/image/width=640,quality=80,format=auto,height=480,fit=cover/https://example.cms.optimizely.com/globalassets/photo.jpg ``` -------------------------------- ### GET /main.css Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/endpoints.md Serves a dynamic stylesheet that generates CSS custom properties and responsive font-size rules. No authentication is required. ```APIDOC ## GET /main.css ### Description Serves a dynamic stylesheet that generates CSS custom properties and responsive font-size rules. No authentication is required. ### Method GET ### Endpoint /main.css ### Authentication None ### Behavior - Produces CSS custom properties from a local `Map` (`font-size-normal: 16px`, `font-size-desktop: 20px`) - Adds responsive `html` font-size rules - Sets `Content-Type: text/css` ### Response #### Success Response (200) Returns CSS text. ### Configuration - runtime = 'nodejs' ``` -------------------------------- ### Theming with ThemeProvider and useTheme (client) Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/components-factory.md Client-side theming example using ThemeProvider, useTheme, and Body components. Provides a button to toggle between light and dark themes based on the effective theme. ```tsx 'use client' import { ThemeProvider, useTheme, Body } from "@/components/theme" export function Frame() { const { theme, effectiveTheme, setTheme } = useTheme() return ( ) } ``` -------------------------------- ### GET /.well-known/optimizely/preview-psk Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/endpoints.md Renders a signed preview page for external systems. Requires an HMAC-SHA256 signature for authentication. Returns a 404 on any validation failure. ```APIDOC ## GET /.well-known/optimizely/preview-psk ### Description Renders a signed preview page for external systems. Requires an HMAC-SHA256 signature for authentication. Returns a 404 on any validation failure. ### Method GET ### Endpoint /.well-known/optimizely/preview-psk ### Parameters #### Query Parameters - **key** (string) - Required - Content key to render. Missing key results in a 404. - **loc** (string) - Optional - Locale. Default is `"en"`. - **ver** (string) - Optional - Content version. - **auth** (string) - Required - Base64url HMAC-SHA256 of `key=&ver=&loc=` (URLSearchParams order) keyed with the site's `publishToken`. ### Authentication HMAC-SHA256 signature required. ### Response Rendered `` HTML on success. 404 via `notFound()` on any validation failure (no token configured, signature mismatch, no key, or `getContentById` total ≠ 1). ### Configuration - `dynamic = "force-dynamic"` - `fetchCache = "force-no-store"` - `revalidate = 0` - `runtime = "nodejs"` ``` -------------------------------- ### Basic search with limit and start using contentSearch Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/lib-api-search.md Server action that calls contentSearch with limit, start, and personalize options. Returns mapped items with title and default URL. ```TypeScript "use server" import contentSearch from "@/lib/api/search"; export async function simpleSearch(term: string) { const results = await contentSearch(term, { limit: 10, start: 0, personalize: false }) return results.items.map(x => ({ title: x.title, url: x.url?.default })) } ``` -------------------------------- ### GET /search Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/endpoints.md Renders an advanced search page that queries content via the `/api/content/search` endpoint using SWR. No authentication is required. ```APIDOC ## GET /search ### Description Renders an advanced search page that queries content via the `/api/content/search` endpoint using SWR. No authentication is required. ### Method GET ### Endpoint /search ### Authentication None ### Behavior - Renders `SearchBox`/`SearchHeader`/`Search` (client components) - Queries `/api/content/search` through SWR - Includes `OptimizelyOneGadget` with `servicePrefix='/api/me'` in the layout ### Response #### Success Response (200) Returns the search page HTML. ### Configuration - runtime = "nodejs" - revalidate = 30 ``` -------------------------------- ### Environment-driven configuration (.env) Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/fx-cli-app.md Environment variables that configure the CLI globally. These values feed the global option defaults, so `opti-fx pull` and `opti-fx setup` can run without repeating credentials. ```dotenv OPTIMIZELY_FX_SDKKEY=your-sdk-key OPTIMIZELY_FX_PROJECT=123456 OPTIMIZELY_FX_PAT=your-personal-access-token OPTIMIZELY_DEBUG=1 ``` -------------------------------- ### src/commands/index.ts - CLI command registry Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/fx-cli-commands.md Registers the setup and pull command modules in the CLI registry. The commands array orders sub-commands as setup, pull. ```ts import { type CliModuleList } from '../types.js' import SetupCommand from './setup.js' import PullCommand from './pull.js' export const commands : CliModuleList = [ SetupCommand, PullCommand ] export default commands ``` -------------------------------- ### Suspense wrapper for server component reading headers Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/middleware.md Example of wrapping a server component in Suspense to opt into streaming while using header values. The component reads x-visitorid / x-search via next/headers. ```tsx import { Suspense } from "react"; export function SomePage() { return ( {/* Server component that reads x-visitorid / x-search via next/headers */} ); } ``` -------------------------------- ### GET /api/content/search Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/lib-api-search.md HTTP endpoint that performs a content search. Requires a `query` parameter, parses optional `limit`, `start`, and `facets` parameters, and always uses locale `en` with personalization enabled. ```APIDOC ## GET /api/content/search ### Description HTTP endpoint that performs a content search. Requires a `query` parameter, parses optional `limit`, `start`, and `facets` parameters, and always uses locale `en` with personalization enabled. ### Method GET ### Endpoint `/api/content/search` ### Parameters #### Query Parameters - **query** (string) - Required - The search term. If empty, returns 400 with error. - **limit** (number) - Optional - Maximum number of results. Default is 12. - **start** (number) - Optional - Starting index. Default is 0. - **facets** (object) - Optional - JSON string parsed as `Filters` object with `ctype` and `locale` fields. ### Request Example ``` GET /api/content/search?query=example&limit=10&start=0&facets={"ctype":"page"} ``` ### Response #### Success Response (200) - Returns search results with items, total, paging, and personalization flag. #### Error Response (400) - **error** (object) - Contains `type` and `message` fields. - **type** (string) - "Bad Request" - **message** (string) - "The term parameter is required" #### Response Example ```json { "term": "example", "total": 1, "paging": { "start": 0, "limit": 10, "pages": 1 }, "items": [ { "title": "Example Page", "url": { "default": "/example" } } ], "isPersonalized": false } ``` ``` -------------------------------- ### URL-driven pagination state with useStringUrlState and useIntUrlState Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/lib-hooks.md Example of using URL state hooks to manage pagination. The Pager component reads the query parameter and updates the start offset, disabling the button when no query is present. ```tsx 'use client' import { useStringUrlState, useIntUrlState } from "@/lib/use-url-state" export function Pager() { const [start, setStart] = useIntUrlState("start", 0, (x) => x >= 0) const [query] = useStringUrlState("query", "") return } ``` -------------------------------- ### getInstance function Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/opti.md Initializes the Optimizely FX client, loading the datafile from Edge Config or CDN. Memoized with React's cache() and reused for the process lifetime. Throws when OPTIMIZELY_FX_SDKKEY (or OPTIMIZELY_FX_SDK_KEY / OPTIMIZELY_SDK_KEY) is missing, when the datafile cannot be loaded, when the client fails to construct, or when onReady() reports failure. ```ts export const getInstance = cache(async () => { const { sdkkey } = readConfigFromEnv() if (!sdkkey) throw new Error("Optimizely Feature Experimentation key not present") let datafile = await getOptimizelyDataFile(sdkkey) if (!datafile) throw new Error("Unable to load datafile, check your sdkKey") const fx = createInstance({ projectConfigManager: createStaticProjectConfigManager({ datafile }) }) if (!fx) throw new Error("Optimizely Feature Experimentation not created") const { success, reason } = (await fx.onReady()) as ({ success: boolean, reason: string }) if (!success) throw new Error("Optimizely Feature Experimentation initialization failed: " + (reason ?? "")) return fx }) ``` -------------------------------- ### getInstance Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/opti.md Initializes and returns the Optimizely Feature Experimentation client. It loads the datafile from Edge Config or CDN, creates the client, and waits for readiness. The result is memoized for the process lifetime. ```APIDOC ## getInstance ### Description Initializes and returns the Optimizely Feature Experimentation client. It loads the datafile from Edge Config or CDN, creates the client, and waits for readiness. The result is memoized for the process lifetime. ### Method Function ### Signature ```ts export const getInstance = cache(async () => { ... }) ``` ### Parameters None; configuration is resolved from the environment. ### Return - The initialized Optimizely FX client (`@optimizely/optimizely-sdk` instance). ### Throws - `Error` when `OPTIMIZELY_FX_SDKKEY` (or `OPTIMIZELY_FX_SDK_KEY` / `OPTIMIZELY_SDK_KEY`) is missing. - `Error` when the datafile cannot be loaded. - `Error` when the client fails to construct. - `Error` when `onReady()` reports failure. ### Datafile Lookup Order 1. Edge Config key `optimizely-fx-` 2. `https://cdn.optimizely.com/datafiles/.json` ``` -------------------------------- ### Register factory during page-route setup (server) Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/components-factory.md Registers the component factory during page-route setup on the server. Imports the factory and setupFactory from the components factory module, and exports a registerExtra function that registers additional component dictionaries. ```ts import "server-only"; import { factory, setupFactory } from "@components/factory"; import { CmsFactory } from "@components/cms"; // facade: the factory is already populated by import export { setupFactory }; export function registerExtra(dict: ComponentTypeDictionary) { factory.registerAll(dict); } ``` -------------------------------- ### GET /api/content/articles Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/lib-api-articles.md HTTP endpoint for retrieving articles with filtering and pagination. ```APIDOC ## [GET] /api/content/articles ### Description Retrieves a list of articles with optional filtering and pagination. The endpoint reads query parameters for locale, author, published date, page size, and page number, and returns a JSON response with article data. ### Method GET ### Endpoint /api/content/articles ### Parameters #### Query Parameters - **locale** (string) - Required - The locale for the articles. Returns 400 if absent. - **author** (string) - Optional - Filter articles by author. - **published** (string) - Optional - Filter articles by published date. - **pageSize** (integer) - Optional - Number of articles per page. Defaults to 12 if not provided or 0. - **page** (integer) - Optional - Page number to retrieve. Defaults to 1 if not provided or 0. ### Response #### Success Response (200) Returns a JSON object with the article data, including total results, total pages, page size, current page, facets, and items. #### Response Example ```json { "locale": "en", "totalResults": 100, "totalPages": 9, "pageSize": 12, "pageNumber": 1, "facets": { "author": [], "published": [] }, "items": [] } ``` ### Error Handling - **400 Bad Request**: Returned when the `locale` query parameter is missing. ``` -------------------------------- ### useBreakpoint Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/lib-hooks.md Hook for responsive layout via breakpoints. ```APIDOC ## useBreakpoint() ### Description Hook that returns the current responsive breakpoint. ### Usage Example ```tsx 'use client' import useBreakpoint from "@/lib/use-breakpoint" export function Layout() { const [, bp] = useBreakpoint() return
} ``` ``` -------------------------------- ### Rendering the flags file programmatically Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/fx-cli-templates.md Shows how to import the template engine and context creation function, then render the 'flags' template with a context built from flags. The output is logged to the console. ```typescript import engine from "./src/templates/index.js"; import { createTemplateContextFromFlags } from "./src/commands/_shared.js"; import { type OptiFxFlag } from "./src/operations/index.js"; const flags: OptiFxFlag[] = [ /* from getFlags() */ ]; const ctx = createTemplateContextFromFlags(flags, "123456", "optimizely-fx", "sdk-key", false); const output = await engine.render("flags", ctx); console.log(output); ``` -------------------------------- ### GET /api/me/search Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/endpoints.md Retrieves recent search terms for the visitor. The 'count' query parameter can limit the number of terms returned. ```APIDOC ## GET /api/me/search ### Description Retrieves recent search terms for the visitor. The 'count' query parameter can limit the number of terms returned. ### Method GET ### Endpoint /api/me/search ### Parameters #### Query Parameters - **count** (integer) - Optional - Positive integer; parsed with Number.parseInt. Ignored when ≤ 0 or unparsable; empty string also ignored. ### Response #### Success Response (200) - **terms** (array of strings) - List of recent search terms. #### Response Example { "terms": ["example", "search"] } ### Notes - Authentication: visitor cookie (none required technically; empty results without one). - Behavior: Uses DataPlatform.getLastSearchTerms() de-duplicated with onlyUnique; truncated to 'count' when provided. - Config: GET only; no other exports. ``` -------------------------------- ### GET|POST /api/me/* Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/endpoints.md Optimizely One API proxy endpoint that catches every path under /api/me. It delegates to createOptimizelyOneApi() and handles search, consent, data platform, web experimentation, and content recommendations delivery endpoints. ```APIDOC ## GET|POST /api/me/* ### Description Optimizely One API proxy endpoint that catches every path under /api/me. It delegates to createOptimizelyOneApi() and handles search, consent, data platform, web experimentation, and content recommendations delivery endpoints. ### Method GET or POST ### Endpoint /api/me/* (catches every path under /api/me) ### Parameters #### Path Parameters - **path** (string) - Optional - The remaining path segments after /api/me, used to route to the appropriate Optimizely One service. ### Authentication Per service inside @remkoj/optimizely-one-nextjs, cookie/session based (Session.addVisitorId / getOrCreateVisitorId). ### Behavior Catches every path under /api/me (search, consent, data platform, web experimentation, content recs delivery endpoints exposed by the Optimizely One Next.js package). ### Config - runtime = 'nodejs' - dynamic = 'force-dynamic' - dynamicParams = true - fetchCache = 'default-no-store' ``` -------------------------------- ### setupFactory: Return cached factory Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/components-factory.md Returns the cached singleton ComponentFactory to avoid creating duplicate factory instances. Consumed by src/app/preview/page.tsx and src/app/layout.tsx. ```ts export const setupFactory = () => factory; ``` -------------------------------- ### GET /preview Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/endpoints.md Provides a CMS edit preview. Forces dynamic rendering and disables caching to always show the latest content. ```APIDOC ## GET /preview ### Description Provides a CMS edit preview. Forces dynamic rendering and disables caching to always show the latest content. ### Method GET ### Endpoint /preview ### Response Rendered preview HTML. ### Configuration - `dynamic = "force-dynamic"` - `fetchCache = "force-no-store"` - `revalidate = 0` - `runtime = "nodejs"` ``` -------------------------------- ### getAllFx: Paginated fetch with placeholder substitution Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/fx-cli-operations.md Fetches all items from a paginated API endpoint, iterating through pages until total_pages is reached. Uses page size 25, sets archived=false, and replaces {key} placeholders in the path with credential values (URL-encoded). Throws Error(`${status}: ${statusText}`) on non-OK responses. ```ts export async function getAllFx(path: string, credentials: Credentials & { [propName: string]: string }) : Promise> { // Prepare paging const pageSize : number = 25 let currentPage : number = 0 let totalPages = 0 const fetchedItems : T[] = [] // Build URL & Headers const pageUrl = new URL(path, baseUrl) for (const key in credentials) pageUrl.pathname = pageUrl.pathname.replaceAll(encodeURIComponent(`{${key}}`), encodeURIComponent(credentials[key])) pageUrl.searchParams.set('per_page', pageSize.toString()) pageUrl.searchParams.set('archived', 'false') const headers = { "accept": "application/json", "authorization": `Bearer ${ credentials.accessToken }` } // Loop do { // Move to next page currentPage ++ pageUrl.searchParams.set('page_number', currentPage.toString()) // Fetch page const result = await fetch(pageUrl, { headers }) if (!result.ok) throw new Error(`${ result.status }: ${ result.statusText }`) const responseData : { total_count: number, total_pages: number, last_url: string, create_url: string, items: T[], count: number, url: string, page: number, first_url: string } = await result.json() // Update context totalPages = responseData?.total_pages || 0 fetchedItems.push(...(responseData?.items || [])) } while (currentPage < totalPages) // Return results return fetchedItems } ``` -------------------------------- ### Catching FX instance errors Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/errors.md Use this pattern to handle errors when getting an FX instance. If the instance is unavailable, treat it as a feature unavailable and log a warning. ```typescript // 1. FX instance errors import { getInstance } from "@/opti" try { const fx = await getInstance() // … } catch (e) { console.warn("FX unavailable", e) // treat as feature unavailable } ``` -------------------------------- ### GET /.well-known/vercel/flags Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/endpoints.md Provides Vercel Flags provider data, integrating with Optimizely Feature Experimentation. It enriches flag options with hints based on authorization and fetch errors. ```APIDOC ## GET /.well-known/vercel/flags ### Description Provides Vercel Flags provider data, integrating with Optimizely Feature Experimentation. It enriches flag options with hints based on authorization and fetch errors. ### Method GET ### Endpoint /.well-known/vercel/flags ### Authentication Bearer token verified via Authorization header ### Behavior - Builds flag data from `src/flags.ts` - Enriches each flag's options with hints: - `newOptions == false` → hint `optly-fx-not-authorized` "Using build time Optimizely Feature options" - `newOptions == null` → hint `optly-fx-fetch-error-` "Unable to fetch real time Optimizely Feature options for " ### Response #### Success Response (200) Returns `ApiData` from `@vercel/flags`. ### Configuration - GET only ``` -------------------------------- ### TemplateEngine render and addTemplate usage Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/fx-cli-tplengine.md Imports TemplateEngine, creates a registration map, and demonstrates rendering with merged object values, fallback to registered template when file is missing, and runtime template extension. ```TypeScript import { TemplateEngine } from "./src/tplengine/index.js"; const registrations = { greet: { template: "Hello {{name}}", values: { lang: "en" }, }, } as const; const engine = new TemplateEngine(registrations); // Merged object-values path const a = await engine.render("greet", { name: "World" }); // "Hello World" // Override path (file missing -> falls back to registered template) const b = await engine.render("greet", { name: "There" }, "./missing.handlebars"); // Extend at runtime const extended = engine.addTemplate("bye", { template: "Bye {{name}}" }); const c = await extended.render("bye", { name: "World" }); ``` -------------------------------- ### GET /api/me/consent Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/endpoints.md Retrieves or sets the user's tracking consent. The 'set' query parameter can be used to update the consent cookie, and the response indicates the current tracking allowance. ```APIDOC ## GET /api/me/consent ### Description Retrieves or sets the user's tracking consent. The 'set' query parameter can be used to update the consent cookie, and the response indicates the current tracking allowance. ### Method GET ### Endpoint /api/me/consent ### Parameters #### Query Parameters - **set** (string) - Optional - Values: 'true' or 'false' (case-insensitive). When absent, the 'opt_in' cookie value is used; with no cookie, consent defaults to true. ### Response #### Success Response (200) - **allowTracking** (boolean) - Indicates whether tracking is allowed. #### Response Example { "allowTracking": true } ### Notes - Authentication: none. - Config: runtime = 'edge', dynamic = 'force-dynamic', dynamicParams = true, fetchCache = 'default-no-store'. - Behavior: When 'set' is provided, the 'opt_in' cookie is set accordingly. Without 'set', the cookie value is used, defaulting to true if no cookie exists. ``` -------------------------------- ### useBreakpoint() Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/lib-hooks.md Documentation for the useBreakpoint hook, which returns the current window width and its mapped breakpoint. ```APIDOC ## useBreakpoint() ### Description Returns the current window inner width and the corresponding Tailwind-style breakpoint. It listens to window resize events and updates the returned values accordingly. ### Signature ```ts export function useBreakpoint(): [number, BreakPoints] ``` ### Parameters None. ### Return Value - **innerWidth** (`number`) - The current `window.innerWidth` value. - **breakPoint** (`BreakPoints`) - The mapped breakpoint: `'xs'` (≤640), `'sm'` (≤768), `'md'` (≤1024), `'lg'` (≤1280), `'xl'` (≤1536), otherwise `'2xl'`. ### Behavior Reads `window.innerWidth` in a `useLayoutEffect` and re-reads on every window `resize` event, updating the breakpoint when the width changes. ``` -------------------------------- ### Advanced getArticles with paging, filters, and error handling Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/lib-api-articles.md Imports 'server-only', getArticles, types, and createClient. Creates a client with nextJsFetchDirectives, cache, and queryCache enabled. Passes paging (count: 12, page: pageNumber) and filters (author: 'Mosey Bank', published: '2025-02-01'). Catches errors, logs 'Article query failed', and returns an empty result object. ```ts import "server-only"; import { getArticles } from "@/lib/api/articles"; import type * as Types from "@/lib/api/articles/types"; import { createClient } from "@remkoj/optimizely-graph-client"; export async function loadArticlePage(pageNumber: number) { const client = createClient(undefined, undefined, { nextJsFetchDirectives: true, cache: true, queryCache: true, }) const paging: Types.PagingData = { count: 12, page: pageNumber } const filters: Types.Filters = { author: "Mosey Bank", published: "2025-02-01" } try { return await getArticles("en", paging, filters, client) } catch (e) { console.error("Article query failed", e) return { locale: "en", totalResults: 0, totalPages: 0, pageSize: 12, pageNumber, facets: { author: [], published: [] }, items: [] } as Types.GetArticlesResult } } ``` -------------------------------- ### GET|POST /.well-known/optimizely/datafile Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/endpoints.md Handles datafile webhook requests. Requires a 'token' query parameter matching OPTIMIZELY_PUBLISH_TOKEN to update the datafile. ```APIDOC ## GET|POST /.well-known/optimizely/datafile ### Description Handles datafile webhook requests. Requires a 'token' query parameter matching OPTIMIZELY_PUBLISH_TOKEN to update the datafile. ### Method GET, POST ### Endpoint /.well-known/optimizely/datafile ### Parameters #### Query Parameters - **token** (string) - Required - Must equal OPTIMIZELY_PUBLISH_TOKEN. ### Response #### Success Response (200) - **message** (string) - "ok" - **data** (object) - The store result from updateDatafile(token). #### Error Response (401 or 500) - **message** (string) - Error message. - **data** (object, optional) - Additional error data. ### Notes - Authentication: query parameter 'token' must equal OPTIMIZELY_PUBLISH_TOKEN. - Behavior: Calls updateDatafile(token); result is mapped to JSON. - Config: runtime = 'edge', both GET and POST. ``` -------------------------------- ### contentSearch(term, options) Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/lib-api-search.md A server-side function that performs a content search with configurable limit, start, filters, and personalization. Returns search results with items, total count, and paging information. ```APIDOC ## contentSearch(term, options) ### Description Performs a content search with configurable limit, start, filters, and personalization. Returns search results with items, total count, and paging information. ### Method Function call ### Endpoint `contentSearch(term, options)` ### Parameters - **term** (string) - Required - The search term. - **options** (object) - Required - Search options. - **limit** (number) - Optional - Maximum number of results to return. Default is 12. - **start** (number) - Optional - Starting index for pagination. Default is 0. - **personalize** (boolean) - Optional - Whether to personalize results. Default is false. - **filters** (Filters) - Optional - Filter object with `ctype` and `locale` fields. ### Request Example ```ts import contentSearch from "@/lib/api/search"; const results = await contentSearch("example", { limit: 10, start: 0, personalize: false }); ``` ### Response #### Success Response - **term** (string) - The search term. - **total** (number) - Total number of results. - **paging** (object) - Pagination info with `start`, `limit`, and `pages`. - **items** (array) - Array of result items, each with `title` and `url` (object with `default` property). - **isPersonalized** (boolean) - Whether results were personalized. #### Response Example ```ts { term: "example", total: 1, paging: { start: 0, limit: 10, pages: 1 }, items: [ { title: "Example Page", url: { default: "/example" } } ], isPersonalized: false } ``` ``` -------------------------------- ### Advanced faceted search with personalization and error handling Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/lib-api-search.md Server action that uses contentSearch with filters, limit, start, and personalize. Catches errors and returns a fallback empty result with a warning. ```TypeScript "use server" import contentSearch, { type Filters } from "@/lib/api/search"; export async function facetedSearch(term: string, contentType: string) { const filters: Filters = { ctype: contentType, locale: "en" } try { return await contentSearch(term, { filters, limit: 12, start: 0, personalize: true }) } catch (e) { console.warn("Search failed, falling back to empty result", e) return { term, total: 0, paging: { start: 0, limit: 12, pages: 1 }, items: [], isPersonalized: false } } } ``` -------------------------------- ### create: POST new entity Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/fx-cli-operations.md Creates a resource via POST to https://api.optimizely.com/v2/. Logs the URL, headers, and body with console.log. Returns the created resource JSON. Throws Error(`${status}: ${statusText}`) on non-OK responses. ```ts export async function create(entity: string, data: any, { accessToken}: Credentials) { // Prepare const createUrl = new URL(basePath + entity, baseUrl) const headers = { authorization: "Bearer " + accessToken, accept: "application/json", "content-type": "application/json", } console.log(createUrl.href, headers, JSON.stringify(data)) const response = await fetch(createUrl, { method: 'POST', body: JSON.stringify(data), headers }) if (!response.ok) throw new Error(`${ response.status }: ${ response.statusText }`) return response.json() } ``` -------------------------------- ### Ensuring GeoIP attributes with upsertAttributes Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/fx-cli-operations.md Ensures GeoIP attributes exist by calling upsertAttributes with an array of attribute objects and credentials. The attributes include user-agent and geo-country with their descriptions. This is used by the setup command. ```ts import { upsertAttributes } from "./src/operations/index.js"; await upsertAttributes([ { key: "user-agent", description: "Browser" }, { key: "geo-country", description: "GeoIP: Country" }, ], { projectId: "123456", accessToken: process.env.OPTIMIZELY_FX_PAT! }); ``` -------------------------------- ### getUserContext Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/api-reference/opti.md Creates an OptimizelyUserContext for the current request using the visitor ID from the `x-visitorid` header and inferred request attributes. Returns null if FX is not configured. ```APIDOC ## getUserContext ### Description Creates an OptimizelyUserContext for the current request using the visitor ID from the `x-visitorid` header and inferred request attributes. Returns null if FX is not configured. ### Method Function (default export) ### Signature ```ts export async function getUserContext(attributes: UserAttributes = {}): Promise ``` ### Parameters - **attributes** (`UserAttributes` from `@optimizely/optimizely-sdk`) - Optional - Extra attributes merged over the inferred request attributes. ### Return - `OptimizelyUserContext` for the current request, or `null` when FX is not configured (initialization failure is swallowed). ### Throws - `Error("No visitor identifier provided by your middleware")` when the `x-visitorid` request header is absent. ``` -------------------------------- ### POST /api/content/publish (also GET) Source: https://github.com/episerver/cms-saas-vercel-demo/blob/main/_autodocs/endpoints.md Publish webhook endpoint that handles CMS publish payloads. It delegates to the createPublishApi function from the Optimizely CMS Next.js package, with authentication via a publish token and configurable additional paths to publish. ```APIDOC ## POST /api/content/publish (also GET) ### Description Publish webhook endpoint that handles CMS publish payloads. It delegates to the createPublishApi function from the Optimizely CMS Next.js package, with authentication via a publish token and configurable additional paths to publish. ### Method POST (also GET) ### Endpoint /api/content/publish ### Parameters #### Request Body - **CMS publish payload** (object) - Required - The webhook payload from the CMS in the format consumed by createPublishApi. ### Response Delegated to createPublishApi. Returns JSON status; 4xx on invalid token. ### Configuration - **additionalPaths** (array of strings) - Paths to always publish (optimized and non-optimized): ['/api/content/search', '/api/content/articles', '/api/me/[[...path]]', '/sitemap.xml', '/robots.txt'] - **optimizePublish** (boolean) - true - Try to publish only the content item published by the CMS - **client** (function) - Graph client used to resolve paths to publish, created with createClient(undefined, undefined, { nextJsFetchDirectives: true, cache: false, queryCache: false }) ### Config Exports - dynamic = 'force-dynamic' - dynamicParams = true - revalidate = 0 - fetchCache = 'force-no-store' - runtime = 'nodejs' ```