### 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'
```