Vipin Banka
Aug 31, 2026
visibility 24
star star star star star
(0 votes)

Serving Heavy Multilingual Content Efficiently with Optimizely SaaS CMS

When building enterprise-grade applications on modern headless stacks, performance is governed by payload efficiency. Optimizely SaaS CMS and Optimizely Graph provide a fast, flexible foundation for omnichannel digital experiences. As projects grow and complex content requirements emerge — such as multi-locale video transcripts, technical specifications, or localized legal disclosures — architects face a challenge that applies to any headless platform and any frontend channel:

How do we store rich, multi-locale data within a content item without bloating delivery payloads or inflating content item counts?

The good news is that Optimizely SaaS CMS provides native architectural levers — granular Graph indexing modes, inline component lists, and SDK query synthesis — that allow teams to solve this challenge elegantly across any channel. This article walks through how to configure these capabilities into a high-performance, cost-efficient omnichannel architecture.


What This Article Covers

This guide is for solution architects and senior developers building enterprise digital experiences with Optimizely SaaS CMS, Optimizely Graph, and the Optimizely Content JS SDK. The patterns apply equally to any frontend channel — Next.js, mobile apps, Vue/Nuxt, SvelteKit, Astro, or embedded displays.

It connects key platform capabilities into a unified architectural pattern:

Platform Capability

Architectural Purpose

Inline Component List Properties

Attach structured multi-locale data to a page without creating standalone items that count toward subscription quotas

Granular Graph Indexing Modes

Use Default vs. Queryable indexing to protect schema quotas and avoid silent data loss

Content JS SDK Query Synthesis

Control which properties are fetched on initial page render by managing registered TypeScript models

Two-Way GraphQL Operations

Target specific language entries on demand and enable catalog-level discovery filtering

Structured Editorial UX

Give content authors clean, form-based input fields grouped logically in dedicated tabs


What You Will Achieve

A production-ready blueprint for storing large, optional multilingual content inside a single Optimizely CMS page item, serving it lazily per locale on demand across any channel, and managing it reliably in the CMS — all while maintaining optimal page performance and zero licensing overhead.


Architecture at a Glance

The pattern spans four layers, each leveraging a native Optimizely capability:

  1. Storage Layer (SaaS CMS): Define a reusable component content type — a language code and a content body — attached as a list property to the page. Entries are versioned inline as part of the page item, not as standalone objects.

  2. Indexing Layer (Optimizely Graph): Set the language code to Queryable (enabling catalog-level filters) and the text body to Default (stored and returned for delivery, bypassing full-text search limits and minimizing schema field usage).

  3. Fetching Boundary (Content JS SDK): Omit the list property from the frontend model registered with initContentTypeRegistry. The SDK's dynamic runtime query builder fetches only what is declared in your model, keeping initial payloads light.

  4. Delivery Layer (On-Demand Fetching): Retrieve the content asynchronously via a targeted GraphQL query only when the user actively requests it — on any channel, from any client.


The Headless Payload Tradeoff

In high-performance web and mobile applications, initial payloads should contain only what is immediately visible. A 15-minute video transcript across multiple languages can easily exceed 200KB of raw text.

Without intentional architecture, three problems emerge:

  • Initial payload bloat: Returning all language transcripts on standard content queries bloats the response and slows Time to First Byte regardless of the rendering framework.

  • Content item proliferation: Creating a standalone shared block per language per video multiplies content items across environments. Optimizely licensing is tied to item counts across all environments in an instance, so this compounds quickly.

  • Translation dependencies: Language-specific properties require a translated page version to exist before auxiliary data can be stored — a hard dependency that rarely matches how transcription pipelines or workflows actually operate.

By configuring Optimizely SaaS CMS correctly, all three challenges are resolved simultaneously.


Step 1: Model the Component as an Inline List Property

First, define a reusable component content type holding two properties: an identifier for the language code and the transcript body.

The Component Definition (VideoTranscript)

{
  "key": "VideoTranscript",
  "baseType": "_component",
  "displayName": "Video Transcript",
  "properties": {
    "langCode": {
      "type": "string",
      "displayName": "Language Code",
      "description": "IETF locale tag, lowercase — for example en-us or de-de",
      "indexingType": "queryable"
    },
    "content": {
      "type": "string",
      "displayName": "Transcript Text"
    }
  }
}

With the component defined, attach it to your page type as an array of inline components ("type": "component"). In Optimizely SaaS CMS, the component property type indicates an inline-only composition, ensuring that entries are embedded directly within the parent page's storage and lifecycle rather than creating standalone referenced entities.

The Page Type (VideoPage)

{
  "key": "VideoPage",
  "baseType": "_page",
  "displayName": "Video Page",
  "properties": {
    "title": {
      "type": "string",
      "displayName": "Title",
      "localized": true
    },
    "videoUrl": {
      "type": "string",
      "displayName": "Video URL",
      "localized": true
    },
    "transcripts": {
      "type": "array",
      "displayName": "Transcripts",
      "description": "One entry per language. Shared across all locale versions of this page.",
      "localized": false,
      "items": {
        "type": "component",
        "contentType": "VideoTranscript"
      }
    }
  }
}

Setting localized: false on the list means all language transcripts live on the master locale version of the page. Automated pipelines can populate translations immediately without waiting for localized page versions to exist.

Architectural Tradeoff: component vs. content Property Types

While array properties in Optimizely SaaS CMS can hold primitive types (strings, numbers, booleans) or complex structures, arrays of structured data typically choose between component and content as their item type:

  • "type": "component" (used in this pattern): Inline-only. Entries are permanently embedded within the parent page and cannot be extracted or referenced as standalone blocks. This is the safer choice when keeping item counts flat is a priority.

  • "type": "content" (alternative): Inline or referenced. Provides a future migration path if transcripts ever need to be reused across multiple items — but editors can also unintentionally author entries as standalone shared blocks, which increases repository item counts and can affect licensing costs.


Step 2: Choose the Right Indexing Type for Each Property

Optimizely SaaS CMS gives developers per-property control over how content is indexed in Optimizely Graph:

Indexing Type

Returned in GraphQL

Usable in where Filters

Full-Text Searchable

Key Consideration

Default

Yes

No

No

Ideal for large display text. Bypasses search term limits and minimizes schema field consumption

Queryable

Yes

Yes

No

Ideal for codes and identifiers. Enables structured filtering without text tokenization

Searchable

Yes

Yes

Yes

Enables full-text search. Subject to Elasticsearch's 8,191-byte per-term limit

Disabled

No

No

No

Excluded from the Graph schema entirely

For this pattern:

  • langCode set to Queryable: A short string with negligible indexing cost. Being filterable enables queries such as "return every video that has a German transcript" without fetching transcript bodies.

  • content set to Default: The full transcript is stored in Optimizely Graph and returned when queried, but it is excluded from full-text indexing. Strings marked as Searchable that exceed 8,191 bytes are silently dropped from the index with no error at publish time. Default sidesteps this entirely and consumes fewer fields against the instance's 100,000-field schema quota.


Step 3: Decouple the Frontend Model from the SDK Query Builder

When using automatic routing or calling getContentByPath(), the Optimizely Content JS SDK inspects the models registered in your application and synthesizes a GraphQL query containing every property declared in them. That behavior is the control point: a property absent from the registered model is never requested.

// src/models/VideoPage.ts

// Registered with the SDK.
// transcripts is deliberately absent so the runtime query builder excludes it.
export interface VideoPage {
  _metadata: ContentMetadata;
  title: string;
  videoUrl: string;
}

// Used only by the on-demand query.
export interface TranscriptEntry {
  langCode: string;
  content: string;
}

export interface VideoPageTranscripts {
  transcripts?: TranscriptEntry[];
}

Register only the core model at application bootstrap:

// src/app/layout.tsx
import { initContentTypeRegistry } from '@optimizely/cms-sdk';
import { VideoPage } from './models/VideoPage';

initContentTypeRegistry([
  {
    type: 'VideoPage',
    model: VideoPage
  }
]);

Splitting the interfaces makes accidental inclusion of the heavy field a compile error rather than a silent payload regression. The property remains fully queryable through dedicated operations because it exists in the Graph schema.


Step 4: Two-Way Querying via Optimizely Graph

Defining langCode as a structured, Queryable property unlocks a powerful two-way querying synergy in Optimizely Graph: targeted on-demand retrieval for a single item, and catalog-level discovery filtering across your entire content repository.

Direction 1: Targeted Transcript Retrieval (Item → Language)

When a user on a specific video page clicks "Show Transcript" or selects a subtitle language on any client (web, mobile app, or smart display), your application fires a targeted GraphQL query retrieving only the transcripts property for that specific content ID:

query GetVideoTranscript($id: String!) {
  VideoPage(where: { _metadata: { key: { eq: $id } } }) {
    items {
      transcripts {
        langCode
        content
      }
    }
  }
}

Your client (or a lightweight server route / API handler) executes this query against Optimizely Graph, resolves the matching langCode entry in memory, and caches the response. Initial page rendering remains instant because zero transcript bytes were fetched upfront.

Direction 2: Catalog Discovery Filtering (Language → Items)

In reverse, you can query across your entire video catalog to discover every video that offers a transcript in a specific language — without fetching any transcript text:

query VideosWithGermanTranscripts {
  VideoPage(where: { transcripts: { langCode: { eq: "de-de" } } }) {
    items {
      _metadata { key displayName }
      title
      videoUrl
    }
  }
}

This unlocks listing and search capabilities (e.g., "Show only videos with German subtitles") while keeping query execution fast and lightweight.


Editorial Experience

A structured component list gives content authors form fields — a language code input and a text area per entry — rather than a raw text box holding serialized data. Entries can be added, reordered, and removed through the standard list interface in the CMS editor.

Two configuration refinements are worth applying:

  • Group the transcripts property into a dedicated tab (Metadata, or similar) in the content type settings, so the primary editing surface stays focused on the content editors work with most.

  • Standardize language codes as lowercase IETF tags and document the convention in the property's help text. Your application's locale fallback logic depends on predictable casing.


Omnichannel Applicability

This pattern is not specific to any frontend framework. The storage and indexing decisions live entirely within Optimizely SaaS CMS and Optimizely Graph. Any channel consuming content through the Graph API can apply the same approach:

  • A mobile app (iOS, Android, React Native, Flutter) fetches core content on screen load and requests transcripts via a separate API call when the user opens the subtitle panel.

  • A Next.js, Nuxt, or SvelteKit application fetches core content during page render and fetches transcripts lazily on user interaction.

  • A kiosk or digital signage display requests transcripts only when the user activates accessibility mode.

The boundary between what is fetched by default and what is fetched on demand is defined by the component model and the SDK registration — not by the channel.


What This Pattern Delivers

Dimension

Fetching everything by default

Selective loading with a component list

Initial payload

Includes all language content on every render

No transcript bytes during initial load

Page and screen performance

Slower TTFB and delayed first render

Payload proportional to visible content

Content item count

Multiplies per language when using shared blocks

Unchanged — entries are stored inline

Graph field usage

Higher when properties are searchable

One Queryable field, one Default field

Byte-limit exposure

Silent index drops above 8,191 bytes

Avoided by using the Default indexing type

Translation dependency

Blocked until the page is translated

Decoupled — entries live on the master locale

Data integrity

Errors surface at render time

Validated at the API boundary on write

 

The underlying principle applies well beyond transcripts. Any property that is large, optional, and consumed by a subset of users — specification sheets, legal text, changelogs, structured FAQ data — benefits from the same separation between what the CMS stores and what any given channel requests by default.


The code samples in this article illustrate the pattern rather than serving as a drop-in implementation.

Aug 31, 2026

Comments

error Please login to comment.
Latest blogs
The Under-Documented Optimizely Caching API I Somehow Missed

I recently discovered some useful, but poorly documented, caching features in Optimizely CMS. Here’s how ReadThrough, ReadStrategy.Wait and master...

Dom Reilly | Aug 31, 2026 |

Beyond [Authorize]: Function-Level Permissions in Optimizely

Why basic role checks fall short and how Permissions to Functions unlock audience-based access control The Problem with Basic Authorization ASP.NET...

Sanjay Kumar | Aug 28, 2026

Insights from upgrading Optimizely CMS 12 to CMS 13

Optimizely CMS 13 has been here a while now, so I wanted to share some insights from a few of the migrations that has been done. The framework and...

Pär Wissmark | Aug 28, 2026 |