Skip to main content

Use sections in pages outside Studio

Put a Studio section on a page your app renders itself, with no template and no Studio-owned route. You need this when most of a page is code but one region, such as a card or a band, should stay editable in Studio.

On a template, a section's data resolves itself. Off a template, one part of that stops, and one only. You take over the job the template was doing. Everything else behaves exactly as it did.

This guide embeds sections. To render a template on a route you own, see Wire template preview routes.

The example on this page is the Author card section on the Authors page and the Latest posts band section on the Archive page, in the Blog project. The app owns an Authors page, an Archive page and a Post page. The Authors page renders the Author card section once for each of Priya, Arjun and Mei. The Archive page renders the Latest posts band section, a Repeater of PostCard over the Latest posts query. The Post page renders the Post hero section for AI 101.

Prerequisites for embedding a section​

  • You've installed and initialized the current Studio SDK in your app. See Install the Studio SDK. In the examples, sdk is the object you get from studioSdk.init.
  • You've registered the components the section uses. See Register a component.
  • You have a Studio section, and you've deployed it. Deploy puts it on your site. To create one, see Build sections. For deploying, see Save, preview and deploy. Your code passes the section's Section UID as compositionUid. The Edit Section dialog shows it.
  • If the section has a linked schema, you have the data its bindings expect, ready to pass as context.
  • If you run the server examples, you use the Next.js App Router. A client-only app skips Render an embedded section on the server. Other server-rendering frameworks aren't covered here.

The Edit Section dialog for the Author card section, with the name Author card section and the Section UID author_card_section.

What embedding a section gives you​

A template decides which sections it shows and where its data comes from. Here your page does that job and renders the section.

When you finish, the section renders on your page with the layout and styles it has in Studio. Check the page in a normal browser tab, not the Studio canvas.

Embed a section with a skill​

  • Skills. Ask your agent to embed a Studio section, and it uses embed-composition. That skill covers only the approach where the section stores its data. It adds the component and the fetch to your page, and you check the page in a normal browser tab. See What are skills?.

What changes off a template, and what doesn't​

A section can read from three places. Only the first one changes when the section leaves a template:

Where the values come fromOn a templateOn your own page
The linked schema — the fields the section was built againstThe template passes its entry down, and the bindings resolve against itYou pass it. Nothing else does
Pinned Entries and Pinned Queries — content the section holds itselfThe SDK fetches themThe SDK fetches them. Unchanged
Component Data — anything your app holds, such as a signed-in userYour code passes it inYour code passes it in. Unchanged

So there is one new job, and it is the template's old one: supply the context the linked schema binds against. You pass it as context on <StudioComposition>, and it is the same slice the template would have passed — a whole entry for a section linked to a whole content type, or one group, block or reference for a section linked to that field. The SDK wraps it for you.

The other two need nothing new from you:

  • Pinned Entries and Pinned Queries resolve on their own, on a template and off it. The section stores them, and the SDK fetches them every time the section renders. That is why a section built only from pinned sources needs no data from your page at all. See Render the section with data stored on it.
  • Component Data works the same in both places. You hand values to the section in code, with data on <StudioComponent>, or on a <Slot> for the components inside it. A template author does exactly the same thing.

Pinned Entries, Pinned Queries and Component Data all live on the section's Data tab, which appears only when Enable Freeform Feature is on for the project.

To pass values from your app to components inside a section, see Bring your own data and StudioComposition and Slot props.

Diagram. A section on a page your app owns takes its values from three places. You pass the linked schema's data as context to StudioComposition, which is the job a template would have done. The section holds its own Pinned Entries and Pinned Queries, which the SDK fetches either way. You pass anything else as Component Data on StudioComponent or a Slot, exactly as you would on a template.

Render the section with data from your page​

Use <StudioComposition> when your page has the data. It renders the section against a context object you pass in, and it fetches no content.

context is the data the section's bindings read. A binding to name reads context.name. Pass the item as you have it, without a wrapper object. In this example, the Author card reads name, photo.url and link.href, so each item in authors has a name, a photo with a url, and a link with an href.

Give <StudioComposition> the context and one of these:

  • A spec you fetched with sdk.fetchComposition. This works on the client and the server.
  • A compositionUid. The component fetches the section itself, on the client only.

For every prop, see StudioComposition and Slot props.

// "use client" on the client. The compositionUid path fetches in the browser.
import { StudioComposition } from "@contentstack/studio-react";

export function AuthorListItem({ person }: { person: Person }) {
return (
<StudioComposition
compositionUid="author_card_section"
context={person}
loadingFallback={<CardSkeleton />}
errorFallback={<CardMissing />}
/>
);
}

loadingFallback and errorFallback both default to null. Without them, a slow fetch and a failed one look the same. Nothing appears. Pass both.

Each <AuthorListItem> shows the Author card with that author's name and photo. Until the fetch finishes, it shows <CardSkeleton />.

Pass context, not data. data is a prop of <StudioComponent />, and <StudioComposition> ignores it.

<StudioComposition> also doesn't use the values typed on the section. A prop bound to a typed value shows its default. To use typed values, see Render the section with data stored on it.

Render the section once for each item in a list​

The Authors page renders the Author card once for each author. Fetch the spec once, above the loop, and reuse it. On the server, spec and context must be plain values. See Render an embedded section on the server.

// app/authors/page.tsx
import { sdk } from "@/lib/contentstack";
import { StudioComposition } from "@contentstack/studio-react";

export default async function AuthorsPage() {
const [authors, spec] = await Promise.all([
getAuthors(),
sdk.fetchComposition({ compositionUid: "author_card_section", searchQuery: "" }),
]);

return (
<div className="author-grid">
{authors.map((person) => (
<StudioComposition key={person.name} spec={spec} context={person} />
))}
</div>
);
}

sdk.fetchComposition returns the section's structure with empty data. You supply the data through context. Each <StudioComposition compositionUid> fetches on its own, so a loop of them makes at least one request per author.

A section renders as its contents, with no element around them. In a CSS grid, put each <StudioComposition> in its own <div>, or the pieces of one card become separate grid cells.

The Authors page now shows three Author cards, one for each author in authors.

The Authors page in a browser, with three Author cards side by side for Priya, Arjun and Mei, each with the About the author heading, a line of text, a photo and the name.

Render the section with data stored on it​

Use useCompositionData when the section stores its own data. In the Blog project, Enable Freeform Feature is on, so the section has a Data tab with the Pinned Entries, Pinned Queries and Component Data cards. If your project has it off, turn it on in Project settings.

The Configuration page in the settings of the Blog project, with Enable Freeform Feature switched on and Save disabled.

The Data tab of the Latest posts band section, with the Latest posts query saved under Pinned Queries for the blog_post content type.

To bind a prop, see Bind a component. To save the Latest posts query, see Add a query.

The Latest posts band stores a Pinned Query, so your page passes no data to it.

"use client";
import { useEffect } from "react";
import { useCompositionData, StudioComponent } from "@contentstack/studio-react";

export function LatestPostsBand() {
const { specOptions, isLoading, error } = useCompositionData({
compositionUid: "latest_posts_band",
});

useEffect(() => {
if (error) console.error(error);
}, [error]);

if (isLoading || error || !specOptions?.spec) return null;
return <StudioComponent specOptions={specOptions} />;
}

Put <LatestPostsBand /> on the Archive page, between your own header and footer. The hook returns specOptions, isLoading, error, refetchSpec and refetchData. The hook doesn't log its error, so the example logs it. When it works, the Archive page shows the PostCards from the Latest posts query.

For where a section's values can come from, see Data sources reference.

The Archive page in a browser, with the Latest posts band between the page&#39;s own header and footer, showing three post cards: AI 101, Featured and Getting started with prompts.

Render an embedded section on the server​

A <StudioComposition> can't fetch by compositionUid during a server render, because the fetch runs in the browser. Fetch the spec on the server and pass spec, as the loop above does.

<StudioComposition> is a Client Component, so spec and context pass from the server to the client. Pass plain values, such as objects, arrays, strings, numbers, booleans and null. Next.js can't send functions or class instances from the server to the client.

For the useCompositionData approaches, fetch on the server and pass the result to a Client Component. The Archive page then shows the same band as the client version:

// app/archive/page.tsx, a Server Component
import { sdk } from "@/lib/contentstack";
import { LatestPostsBandClient } from "./LatestPostsBandClient";

export default async function ArchivePage() {
const specOptions = await sdk.fetchCompositionData({
compositionUid: "latest_posts_band",
searchQuery: "",
});

return <LatestPostsBandClient specOptions={specOptions} />;
}
// app/archive/LatestPostsBandClient.tsx
"use client";
import { StudioComponent, type StudioComponentSpecOptions } from "@contentstack/studio-react";

export function LatestPostsBandClient({
specOptions,
}: {
specOptions: StudioComponentSpecOptions;
}) {
return <StudioComponent specOptions={specOptions} />;
}

On the server, sdk.fetchCompositionData and sdk.fetchComposition reject when they fail. Pass the error to isCompositionNotFound, from @contentstack/studio-react. It's true when Studio finished looking and the section or its entry isn't there, such as COMPOSITION_UID_NOT_FOUND or CONNECTED_ENTRY_NOT_FOUND. Your page still exists, so render it without the section. When it's false, Studio couldn't get an answer, so let the error reach your framework's error page.

import { isCompositionNotFound } from "@contentstack/studio-react";

let specOptions = null;
try {
specOptions = await sdk.fetchCompositionData({
compositionUid: "latest_posts_band",
searchQuery: "",
});
} catch (error) {
if (!isCompositionNotFound(error)) throw error;
}

To choose between client and server rendering, see Choose CSR or SSR.

If an embedded section doesn't work​

Find what you see below. Each problem says why it happens and what to do, and ends with what it looks like.

The SDK is not initialized​

The console says "StudioComposition: SDK not initialized." and the error fallback shows. The compositionUid approach needs the SDK, and studioSdk.init hasn't run. Call studioSdk.init once at app startup. Check that this page imports the module that runs it.

The browser console on the Authors page showing the error &quot;StudioComposition: SDK not initialized.&quot; beside the page, where each author&#39;s card shows the &quot;Author card unavailable&quot; error fallback.

The component says it needs a spec or a compositionUid​

The error is "StudioComposition requires either a spec or a compositionUid." Neither prop reaches the component. A spec that is undefined counts as missing. Pass one. Log the value before you render.

The browser console on the Authors page, filtered to Uncaught errors, showing the error &quot;StudioComposition requires either a spec or a compositionUid.&quot; beside a blank page.

The hook says a UID is required​

The hook returns the error "Either compositionUid, url, or templateContentTypeUid is required". Your compositionUid is undefined, for example because it comes from a variable that isn't set yet. Skip rendering until the UID exists.

The browser console on the Archive page showing the logged error &quot;Either compositionUid, url, or templateContentTypeUid is required&quot; beside the Archive page with no band.

The error is COMPOSITION_UID_NOT_FOUND​

The error is COMPOSITION_UID_NOT_FOUND, No composition with the uid "author_card_section" in content type "compositions". The UID matches nothing, or you haven't deployed the section to the environment your app reads. Check the Section UID spelling and deploy the section. See COMPOSITION_UID_NOT_FOUND.

The browser console on the Authors page showing a CompositionNotFoundError for a Section UID that matches no section, beside the page with the &quot;Author card unavailable&quot; error fallback in each card.

The error is COMPONENT_NOT_REGISTERED​

The error is COMPONENT_NOT_REGISTERED, "These components are used by this composition and not registered: …" The section uses components you haven't registered. Register them before the fetch. See Register a component.

The browser console on the Authors page showing the COMPONENT_NOT_REGISTERED error, which names the components stack, avatar and byline, beside the page with the &quot;Author card unavailable&quot; error fallback in each card.

The error is CONNECTED_ENTRY_NOT_FOUND​

The error is CONNECTED_ENTRY_NOT_FOUND, No entry was found for the content type "blog_post" this composition is connected to. The section has a linked schema, and nothing filled it. Pass the data as context on <StudioComposition>. See CONNECTED_ENTRY_NOT_FOUND.

The browser console on the Post page showing the logged CONNECTED_ENTRY_NOT_FOUND error for the blog_post content type, beside the Post page with no section on it.

Bound values are blank or show their defaults​

context doesn't have the keys the section's bindings read, or you passed data instead of context. <StudioComposition> has no data prop. Open the section in Studio, read the bindings on the blank components, and pass those keys as context.

The Authors page with three Author cards rendered but showing the default name &quot;Author name&quot; and a placeholder photo instead of Priya, Arjun and Mei.

An empty gap where <StudioComposition> should render​

Both fallbacks default to null. Pass loadingFallback and errorFallback, then read the console for the cause.

The Authors page with its header, the Authors title and its footer, and an empty gap between the title and the footer where the Author cards should be.

An empty gap where the useCompositionData section should render​

The hook returned an error or no spec, so the component returned null. Read the logged error, and check the Section UID and that the SDK is initialized.

The Archive page with its header, the Archive title and its footer, and an empty gap between the title and the footer where the Latest posts band should be.

The server sends an empty page and the content appears later​

You passed compositionUid on a server render. Fetch with sdk.fetchComposition on the server and pass spec.

The page source of the Authors page, showing the HTML the server sent: the author-grid div holds three empty placeholder divs and no Author card content.

Next.js reports an error about passing props to a Client Component​

spec or context contains something Next.js can't serialize, such as a function or a class instance. Map your data to plain objects before you pass it.

The Next.js error overlay on the Authors page, saying that functions cannot be passed directly to Client Components and pointing at the formatName function on the author passed as context.

One request per item in a loop​

Each compositionUid mount fetches on its own. Fetch the spec once above the loop and pass spec.

The Network panel on the Authors page, filtered to the section requests: three requests whose query names the author_card_section UID, one for each author.

For the props and the fetchComposition query shape, see StudioComposition and Slot props. To pass values to components inside a section, see Bring your own data.

Report a problem with this page