manus-skills/.kortix/opencode/skills/content/content-experimentation-best-practices/references/cms-integration.md
zhukang 1c7abd81d1 All 310 Manus skill-hub community skills as a Kortix marketplace registry
- 310 registry:skill items, 1528 files, 7 categories (hub tags)
- registry.json generated by @kortix/registry buildRegistry
- nested SKILL.md in sn-da-* renamed SUBSKILL.md (no phantom skills)
- provenance + licenses: SOURCES.md
2026-09-15 13:37:20 +08:00

6.8 KiB

CMS Integration Patterns

Integrating experimentation with your CMS enables content teams to run tests without developer intervention.

Architecture Options

1. CMS-Managed Variants

Store experiment variants as content in the CMS.

Pros: Content team autonomy, version controlled Cons: More complex queries, potential publish coordination

// Experiment document
defineType({
  name: 'experiment',
  type: 'document',
  fields: [
    defineField({ name: 'name', type: 'string' }),
    defineField({ name: 'status', type: 'string', options: { 
      list: ['draft', 'running', 'paused', 'concluded'] 
    }}),
    defineField({ 
      name: 'variants', 
      type: 'array',
      of: [{
        type: 'object',
        fields: [
          defineField({ name: 'name', type: 'string' }),
          defineField({ name: 'weight', type: 'number' }),
          defineField({ name: 'content', type: 'reference', to: [{ type: 'page' }] }),
        ]
      }]
    }),
    defineField({ name: 'startDate', type: 'datetime' }),
    defineField({ name: 'endDate', type: 'datetime' }),
  ]
})

2. Field-Level Variants

Store variants as fields on the content document.

Pros: Simpler queries, content stays together Cons: Less flexible, schema complexity

defineType({
  name: 'landingPage',
  fields: [
    defineField({ name: 'headline', type: 'string' }),
    defineField({ 
      name: 'headlineVariantB', 
      type: 'string',
      description: 'A/B test variant (leave empty if not testing)'
    }),
    defineField({ name: 'activeExperiment', type: 'string' }),
  ]
})

3. External Experimentation Platform

Use dedicated tools (Optimizely, LaunchDarkly, VWO) with CMS content.

Pros: Robust analytics, proven platforms Cons: Additional cost, integration complexity

// CMS stores experiment IDs, platform handles assignment
defineField({
  name: 'experimentId',
  type: 'string',
  description: 'Optimizely experiment ID'
})

Implementation Pattern (CMS-Managed)

1. Experiment Schema

defineType({
  name: 'experiment',
  type: 'document',
  fields: [
    defineField({ name: 'name', type: 'string', validation: r => r.required() }),
    defineField({ name: 'hypothesis', type: 'text' }),
    defineField({ 
      name: 'status', 
      type: 'string', 
      options: { list: ['draft', 'running', 'concluded'] },
      initialValue: 'draft'
    }),
    defineField({
      name: 'variants',
      type: 'array',
      of: [{
        type: 'object',
        name: 'variant',
        fields: [
          defineField({ name: 'id', type: 'string' }),
          defineField({ name: 'name', type: 'string' }),
          defineField({ name: 'weight', type: 'number', initialValue: 50 }),
        ]
      }],
      validation: r => r.min(2).error('Need at least 2 variants')
    }),
    defineField({ name: 'targetPage', type: 'reference', to: [{ type: 'page' }] }),
    defineField({ name: 'targetField', type: 'string' }),
  ]
})

2. Variant Content

// On the page being tested
defineField({
  name: 'experimentVariants',
  type: 'array',
  of: [{
    type: 'object',
    fields: [
      defineField({ name: 'experimentId', type: 'reference', to: [{ type: 'experiment' }] }),
      defineField({ name: 'variantId', type: 'string' }),
      defineField({ name: 'headline', type: 'string' }),
      // Other variant-specific fields
    ]
  }]
})

3. Frontend Assignment

// Middleware or server-side. Returns the assigned variant id, or null when the
// experiment has no variants to assign.
function assignVariant(experimentId: string, variants: Variant[]): string | null {
  if (variants.length === 0) return null

  // Reuse an existing assignment, but only if it's still a valid variant.
  // After variants are renamed or removed, drop the stale cookie and reassign,
  // otherwise users stay bucketed to IDs that no longer exist.
  const cookieKey = `exp_${experimentId}`
  const existing = getCookie(cookieKey)
  if (existing && variants.some(v => v.id === existing)) return existing

  // Random assignment based on weights. Normalize against the total weight so
  // splits work even when weights don't sum to 100 (otherwise draws above the
  // sum skew to the fallback).
  const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0)
  if (totalWeight <= 0) {
    const fallback = variants[0]
    setCookie(cookieKey, fallback.id, { maxAge: 30 * 24 * 60 * 60 })
    return fallback.id
  }
  const rand = Math.random() * totalWeight
  let cumulative = 0
  for (const variant of variants) {
    cumulative += variant.weight
    if (rand < cumulative) {
      setCookie(cookieKey, variant.id, { maxAge: 30 * 24 * 60 * 60 })
      return variant.id
    }
  }
  // Fall back to the last variant to absorb any floating-point remainder, and
  // persist it like any other assignment so the visitor stays bucketed.
  const fallback = variants[variants.length - 1]
  setCookie(cookieKey, fallback.id, { maxAge: 30 * 24 * 60 * 60 })
  return fallback.id
}

4. Query with Variant

Resolve one experiment's assignment at a time, passing both its experimentId and the variantId returned by assignVariant. Match on both: variant IDs like control repeat across experiments, so filtering on variantId alone could pick a different running experiment's row. For a page running several experiments, resolve each one separately and merge the results.

*[_type == "page" && slug.current == $slug][0]{
  ...,
  "experiment": experimentVariants[
    experimentId->_id == $experimentId &&
    experimentId->status == "running" &&
    variantId == $variantId
  ][0]{
    experimentId->{name, _id},
    variantId,
    headline
  }
}

Analytics Integration

Event Tracking

// Track experiment exposure
function trackExposure(experimentId: string, variantId: string) {
  analytics.track('Experiment Viewed', {
    experimentId,
    variantId,
    timestamp: new Date().toISOString()
  })
}

// Track conversion
function trackConversion(experimentId: string, variantId: string, metric: string) {
  analytics.track('Experiment Conversion', {
    experimentId,
    variantId,
    metric,
    timestamp: new Date().toISOString()
  })
}

Data Layer

// Push to data layer for analytics tools
window.dataLayer.push({
  event: 'experiment_assignment',
  experiment_id: experimentId,
  variant_id: variantId
})

Best Practices

Content Team Workflow

  1. Create experiment document with hypothesis
  2. Create variant content
  3. Set status to "running"
  4. Monitor results
  5. Set status to "concluded" and record winner

Avoid Flicker

  • Assign variants server-side when possible
  • Use CSS to hide content until variant determined
  • Pre-render both variants, show based on assignment

Clean Up

  • Archive concluded experiments
  • Remove losing variant content
  • Implement winner as default