---
name: embed-composition
---

## When to use

Embed a Studio-managed editable region inside a code-owned page using `useCompositionData({ compositionUid })`. Section-in-Slot, not raw component.

Use when exposing ONE editable region inside a code-owned page (marketing band on a PDP, promo strip, recommendations shelf) without making the whole page Studio-managed. Phrases: "embed a Studio band", "marketer-editable PDP region", "promo strip", "one editable section". Skip if the user wants the whole page in Studio (`migrate-page-to-studio`) or every URL through Studio (`setup-template-preview-routes`).

> **Auth preflight: settle the credential before the first API call.** Resolve it OAuth-first per [`authenticate-cma`](authenticate-cma.md): `CS_OAUTH_ACCESS_TOKEN`, else the Contentstack MCP's stored session: Read that skill for where it is, even with no MCP connected. **Never ask the user for a session `authtoken`.** If nothing resolves, or a refresh fails with `400 invalid_refresh_token`, hand them `! CONTENTSTACK_REGION=<code> npx @contentstack/mcp --auth` (it needs a TTY and a browser, so it cannot be run for them) and wait. `403 error_code 316` is a valid credential aimed at another org: fix the org or the `api_key`, do **not** re-authenticate.

# Embed a Studio-managed region inside a code-owned page

## Context

This skill operationalizes the [Embed composition recipe](https://studio-documentation.contentstackapps.com/templates-and-sections/use-sections-in-pages-outside-studio). The user has a code-owned page (PDP, checkout, home, account). One region should be marketer-editable without code changes.

Mechanism: `useCompositionData({ compositionUid })` resolves a known composition by its slug. The result hands to `<StudioComponent specOptions={specOptions} />` rendered inline. A Section composition renders correctly through this path.

## Pre-flight checks

Stop and report if any are missing:

1. **`install-studio` already ran.** Check `package.json` for `@contentstack/studio-react`, and verify a stack init exists (typically `lib/contentstack.ts` or `lib/studio.ts` with `export const sdk = studioSdk.init({...})`).
2. **Components are registered** at the app shell, at least the components that the embedded composition uses.
3. **Studio project is configured**: in Project Settings, Configuration is filled in (Environment, Language, Canvas URL pointing at the section-authoring route).
4. **Target page file exists** and contains the `insertionAnchor`. If the anchor isn't found, report the file's first 20 lines and ask the user to specify a better anchor.

## Task

### Step 1: Show the user the Studio UI steps for creating the composition

Print this VERBATIM (substitute `{bandName}` and `{compositionFlavor}`):

```
First, you'll create the composition manually in Studio. Open
https://app.contentstack.com → your stack → Studio → your project →
Sections → "+ New Section".

Configure it as:
  Display Name:    {bandName} (use Title Case — Studio will auto-fill)
  Section UID:     {bandName}   ← the composable_uid the embed will reference
  Description:     "Embedded band inside <target page>. Edited by marketing;
                    rendered inline by code via useCompositionData."

There's no URL to configure. Studio previews a section on the
section-authoring canvas.

SAVE the section. Then come back here and I'll wire the React side.
```

Then PAUSE and wait for the user to confirm: "Composition created." Do NOT proceed until they confirm: the embed code references the `composable_uid` that doesn't exist yet.

### Step 2: Scaffold the `<StudioEmbed>` wrapper (if not present)

> **Clarification: `<StudioEmbed>` is NOT an SDK export.** This skill scaffolds a thin wrapper component **inside the user's codebase** that bundles the SDK's `useCompositionData` hook + `<StudioComponent />` renderer. The SDK ships only those two primitives. The wrapper exists purely to avoid repeating loading/error boilerplate on every embed. The wrapper can be named anything (`<StudioEmbed>`, `<ManagedBand>`, `<EditableRegion>`, etc.). This skill uses `StudioEmbed` as a sensible default. If the user prefers a different name, accept it.

Check whether the project already has such a wrapper component at `src/components/StudioEmbed.tsx` (Vite/CRA) or `app/_components/StudioEmbed.tsx` (Next.js App Router) or any path containing both `useCompositionData` AND `StudioComponent` AND `compositionUid` (`grep -rEn "useCompositionData.*compositionUid" src/ app/ 2>/dev/null | head -3`). If found, reuse it. If not, emit one:

**Vite / React Router / CRA (`src/components/StudioEmbed.tsx`):**

```tsx
"use client";  // harmless in non-Next; Next App Router needs it
import {
  useCompositionData,
  StudioComponent,
} from "@contentstack/studio-react";

interface StudioEmbedProps {
  /** composable_uid slug of the Studio composition to embed */
  compositionUid: string;
  /** rendered while the composition is loading; default: null (no layout shift) */
  fallback?: React.ReactNode;
}

/**
 * Render a Studio-managed composition inline inside a code-owned page.
 * Verified pattern (see docs/15-build/05-templates-and-sections/70-use-sections-in-pages-outside-studio.md).
 *
 *   <StudioEmbed compositionUid="pdp_marketing_band" />
 */
export function StudioEmbed({ compositionUid, fallback = null }: StudioEmbedProps) {
  const { specOptions, isLoading, error } = useCompositionData({ compositionUid });

  if (isLoading) return <>{fallback}</>;
  if (error || !specOptions?.spec) {
    if (process.env.NODE_ENV === "development") {
      console.warn(
        `[StudioEmbed] No composition found for compositionUid="${compositionUid}". ` +
        `Either it's not created yet, the slug is misspelled, or it was deleted in Studio.`,
      );
    }
    return null;
  }
  return <StudioComponent specOptions={specOptions} />;
}
```

**Next.js App Router CSR variant**: same file content, marked `"use client"` (already shown above).

**Next.js App Router SSR variant (when the embed must render server-side)**: emit BOTH a server fetcher and a client wrapper. Use this when the surrounding page is a Server Component:

```tsx
// app/_components/StudioEmbed.tsx — Server Component (no "use client")
import { sdk } from "@/lib/contentstack";    // or @/lib/studio for additive integrations
import { resolveComposition } from "@/lib/resolve-composition";  // configure-csr-vs-ssr
import { StudioEmbedClient } from "./StudioEmbedClient";

export async function StudioEmbed({ compositionUid }: { compositionUid: string }) {
  // fetchCompositionData THROWS when the composition is missing — it does not
  // resolve empty. Unwrapped, a deleted or misspelled embed takes down the whole
  // host page with a 500. An embed must degrade to nothing, never to an outage.
  // NOTE: a compositionUid lookup throws COMPOSITION_UID_NOT_FOUND — NOT the
  // ..._BY_URL id. resolveComposition covers all three not-found ids.
  const { specOptions, notFound } = await resolveComposition(sdk, {
    compositionUid,
    searchQuery: "",       // required on server (no window.location.search)
  });
  if (notFound) return null;   // real failures still throw — see resolveComposition
  return <StudioEmbedClient specOptions={specOptions} />;
}
```

```tsx
// app/_components/StudioEmbedClient.tsx — Client Component
"use client";
import { StudioComponent } from "@contentstack/studio-react";
export function StudioEmbedClient({ specOptions }: { specOptions: any }) {
  return <StudioComponent specOptions={specOptions} />;
}
```

Detect the framework (Next App Router vs Vite/CRA) before picking. Same rule as `install-studio`: presence of `app/` dir + `next` in package.json means App Router. Else default to the client-only variant.

### Step 3: Insert the embed into the target page

Read the target page file. Find the `insertionAnchor`. Insert the `<StudioEmbed compositionUid="{bandName}" />` line at `insertionPosition` (before/after the anchor).

Also add the import at the top of the file:
- Relative path inferred from where the wrapper landed in step 2 (e.g. `import { StudioEmbed } from "@/components/StudioEmbed"`).

Show the user the diff before writing:

```
target page changes (preview before write):

  + import { StudioEmbed } from "@/components/StudioEmbed";

  …
      <ProductSpecs specs={product.specs} />
  +   <StudioEmbed compositionUid="{bandName}" />
      <BuyBox product={product} />
  …

Proceed? [y/n]
```

Wait for explicit confirmation. Then write.

### Step 4: Verify the embed renders

1. Print: "Start your dev server (`npm run dev`) if it isn't running, then visit the target page in a browser, `<embed should appear at insertion point>`."
2. Tell the user: "In another tab, open Studio, go to Sections, and open `{bandName}`. Make a visible change (drop a Hero, change static text), Save, then reload the visitor page. The change should appear without any code redeploy."
3. If nothing renders, the debugging checklist (run these in order):
   - In the browser DevTools Console, look for a `[StudioEmbed]` warning (slug mismatch / not created)
   - In the Network tab, look for the CDA fetch for the composition. Check status code
   - In Studio, verify the composition's `composable_uid` exactly matches `{bandName}` (case-sensitive)
   - If iframe error appears: composition exists but is empty: drop at least one component in Studio + Save

### Step 5: Register the embed in `STUDIO_EMBEDS.md`

For projects with multiple embeds, a single registry file keeps the page-to-compositionUid mapping visible. Create or append to `STUDIO_EMBEDS.md` at the repo root:

```markdown
# Studio Embeds Registry

Each row = one Studio-managed band embedded inside a code-owned page.
Update when adding/removing/renaming embeds.

| Composition UID (slug) | Flavor   | Embedded in                          | Component             | Notes |
|---|---|---|---|---|
| {bandName}             | {compositionFlavor} | `{targetPageFile}`         | `<StudioEmbed compositionUid="{bandName}" />` |  |
```

If the file already exists, append the row before the closing of the table. Print: "Added a row to `STUDIO_EMBEDS.md`. Keep this file updated when the embed list changes."

## Inputs needed from the user

In order. Stop and ask if any is missing.

1. `targetPageFile`: exact file path. The skill reads + modifies it.
2. `bandName`: kebab-case slug. Validate: lowercase, underscores or hyphens only, ≤32 chars. Reject invalid (would break Studio's composable_uid validation).
3. `compositionFlavor`: always `section`. Sections are the intended shape for embedded compositions.
4. `insertionAnchor`: string the skill greps for in the target page file. If multiple matches, ask the user to disambiguate.
5. `insertionPosition`: `before` or `after`. Default `after`.

## Acceptance

This skill succeeds only when ALL are true:

- [ ] User confirmed the Studio composition exists (step 1, explicit "Composition created" response).
- [ ] `<StudioEmbed>` wrapper exists (either reused or scaffolded in step 2).
- [ ] Target page file imports `StudioEmbed` AND renders `<StudioEmbed compositionUid="{bandName}" />` at the chosen position.
- [ ] Visiting the target page in a browser renders the embed (step 4 visual confirmation).
- [ ] `STUDIO_EMBEDS.md` has a row for this embed.

If the embed doesn't render visually, do NOT claim success: surface the debugging checklist from step 4.

## Common pitfalls

| Pitfall | Why it bites | Fix |
|---|---|---|
| Slug typo between Studio + code | `compositionUid` is case-sensitive, `pdp_marketing_band` ≠ `Pdp_Marketing_Band` | Copy the `composable_uid` directly from Studio's composition list. Keep `bandName` in lowercase_snake_case |
| Composition is empty in Studio | `specOptions.spec` resolves but `<StudioComponent>` renders nothing | Drop at least one component in Studio and Save before testing the embed |
| Embed renders during SSR, disappears after hydration | Mixing the SSR variant + CSR variant inadvertently | Use the SSR pattern (server-fetch + client render) when the surrounding page is a Server Component. Use the CSR variant when the surrounding page is `"use client"` |
| Layout shift when band loads | Default `fallback={null}`, so the page reflows when content arrives | Pass `fallback={<div style={{height: 200}} />}` (or a skeleton) matching expected band height |
| Embed breaks after rename in Studio | Renaming a composition changes its `composable_uid`. Embed still references the old slug | Don't rename embedded compositions. If you must, do a global find-replace in the codebase AND update `STUDIO_EMBEDS.md` in the same commit |
| Author edits but no live updates | CDA cache | Republish the composition (Save in Studio is draft. Publish is what visitors fetch) |
| Embed in a Server Component page calls the client-only hook | `useCompositionData` is CSR-only by definition (uses React state) | Use the SSR variant (server-fetch via `sdk.fetchCompositionData` + client-wrap) when the parent is RSC |

## See also

- `docs/25-learn-by-example/05-guides/15-adopt-studio-into-an-app-you-already-ship.md`: when to embed vs use catch-all vs full route swap
- `docs/25-learn-by-example/05-guides/15-adopt-studio-into-an-app-you-already-ship.md`: when to embed vs use catch-all vs full route swap
- `migrate-page-to-studio`: when you want the WHOLE page Studio-managed instead of just one band
- `setup-template-preview-routes`: for the catch-all route pattern that handles full pages
- `install-studio`: prerequisite (SDK + init must be wired before any embed works)
