惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
A
About on SuperTechFans
G
Google Developers Blog
B
Blog RSS Feed
F
Fortinet All Blogs
WordPress大学
WordPress大学
Recent Announcements
Recent Announcements
Hugging Face - Blog
Hugging Face - Blog
Y
Y Combinator Blog
MongoDB | Blog
MongoDB | Blog
小众软件
小众软件
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
T
The Blog of Author Tim Ferriss
Jina AI
Jina AI
IT之家
IT之家
P
Proofpoint News Feed
美团技术团队
量子位
Microsoft Azure Blog
Microsoft Azure Blog
Engineering at Meta
Engineering at Meta
B
Blog
有赞技术团队
有赞技术团队
U
Unit 42

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Placeholder Maintenance with Sitecore Content SDK
Kenneth McAndrew · 2026-06-19 · via DEV Community

During the conversion process from JSS to Content SDK for Sitecore AI (IE XM Cloud), there's some changes to how components are designated and mapped to be aware of, which affect how placeholders work and how those components work inside the placeholders. This led to our needing a solution to handle placeholders in certain circumstances.

Server vs Client Components

The component designation in Content SDK separates server from client components. This separation is determined by the use of hooks in the code (like useEffect) and a component is made "client" by placing "use client" in the first line of the component. Technically you could make every component "client" but you lose out on benefits of server components.

When it comes to Sitecore, there's a useSitecore hook that can be used for getting things like the editing state. But you don't need to use that hook, because the new placeholder model, called AppPlaceholder, that passes the page model down from one level to the next. That includes the edit state, so you can access it in server components as well. This starts all the way up in your Layout.tsx file, which you'll see in the starter kit you can download from Github, and it's a required parameter so it'll always be passed down.

The Challenge

You cannot bring server components into a placeholder under a client component. Sitecore has documentation on this. It'll work if you set things up manually, but in Pages you won't be allowed to pull components in. To be clear, the scenario is you have a component - let's say a tabs component - that has an AppPlaceholder on it. The tabs component will typically have some hooks to handle click changes between the tabs, and that requires "use client" to be designated. Then you'll have a tabs panel component, which you pull in to set up each individual tab, and that also has AppPlaceholder to hold a variety of other components. Then you might pull in a rich text component, which is likely a server component because it has no hooks. If you just do that straight away, like you would have in JSS, you won't be able to bring those server components in.

A Solution

I'll say a solution because I'm sure there's others, and the documentation suggests something, but it wasn't entirely clear to me. What I worked out with some research, AI, and trial and error, was this solution. The solution boils down to this: if you have a component that has AppPlaceholder and needs to be a client component as well, you have a client/server tag team.

The Server Component

The server component part is the actual component you'll be registering in Sitecore as a rendering. It might look something like this:

import { type JSX } from "react";
import { AppPlaceholder } from "@sitecore-content-sdk/nextjs";
import componentMap from ".sitecore/component-map";
import TabsClient from "./TabsClient";
import type { TabsProps } from "./Tabs.types";

/**
 * Tabs — server component wrapper.
 *
 * Owns AppPlaceholder so the Sitecore editor treats the placeholder as
 * server-owned and allows inserting any component type (server or client).
 *
 * Edit mode: renders the placeholder in a single column with a label.
 *
 * Normal mode: passes pre-rendered placeholder content as RSC children to
 * TabsClient, which handles mobile detection and DOM manipulation for the
 * horizontal tab navigation.
 */
export default function Tabs({ rendering, params, page }: TabsProps): JSX.Element {
  const isPageEditing = page?.mode?.isEditing ?? false;
  const phKey = `tabs-${params?.DynamicPlaceholderId ?? ""}`;

  const content = (
    <AppPlaceholder
      name={phKey}
      rendering={rendering}
      componentMap={componentMap}
      page={page}
    />
  );

  if (isPageEditing) {
    return (
      <>
        <section
          id={params?.RenderingIdentifier ?? ""}
          className={`component tabs ${params?.Styles ?? ""}`}
        >
          <div className="container">
            <div className="col-12">
              <h3>Tabs (edit mode)</h3>
              {content}
            </div>
          </div>
        </section>
      </>
    );
  }

  return (
    <>
      <TabsClient
        renderingUid={rendering.uid ?? ""}
        params={params}
      >
        {content}
      </TabsClient>
    </>
  );
}

You'll notice that this takes an AppPlaceholder component and sends it to a component called TabsClient. That's what we'll look at next, and where all the hooks are working at. But this provides a mechanism by which we continue the "chain" of server components, which allows us to use server or client components in the subsequent placeholder tree.

The Client Component

In our case, the tabs component is designed to switch to an accordion if in mobile mode, so there's a useEffect and other hooks to handle resize checks. It also takes that children parameter, which is our AppPlaceholder fed down from the server component, and now it's safe to use server or client components. You can see all this in the code below.

"use client";

import {
  useCallback,
  useEffect,
  useRef,
  useState,
  type JSX,
  type ReactNode,
} from "react";
import type { TabsParams } from "./Tabs.types";

interface TabsClientProps {
  children: ReactNode;
  renderingUid: string;
  params?: TabsParams;
}

/**
 * TabsClient — handles mobile detection and horizontal tab nav DOM
 * manipulation for Tabs. Receives pre-rendered placeholder content as
 * children from the server component wrapper.
 *
 * After mount, moves .navItem <li> elements from the content area into the
 * nav <ul> and activates the first tab.
 */
export default function TabsClient({
  children,
  renderingUid,
  params,
}: TabsClientProps): JSX.Element {
  const containerRef = useRef<HTMLUListElement>(null);
  const [isMobile, setIsMobile] = useState(false);

  const handleResize = useCallback(() => {
    setIsMobile(window.innerWidth < 576);
  }, []);

  useEffect(() => {
    handleResize();
    window.addEventListener("resize", handleResize);

    if (!isMobile) {
      const runMove = () => {
        const elements = document.querySelectorAll(
          `.content-${renderingUid} .navItem`,
        );
        elements.forEach((el) => {
          if (el === elements[0]) {
            const anchor = el.getElementsByTagName("a")[0];
            anchor?.classList.add("active");
            anchor?.setAttribute("aria-selected", "true");

            const panel = document.querySelector(
              `.content-${renderingUid} .tab-pane`,
            );
            panel?.classList.add("active");
            panel?.classList.add("show");
          }
          containerRef.current?.appendChild(el);
        });
      };
      requestAnimationFrame(runMove);
    }

    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, [handleResize, isMobile, renderingUid]);

  if (isMobile) {
    return (
      <section className="component accordions" data-module="accordions">
        <div className="container">
          <div className="box-shadow">{children}</div>
        </div>
      </section>
    );
  }

  return (
    <section
      id={params?.RenderingIdentifier ?? ""}
      className={`component tabs ${params?.Styles ?? ""}`}
    >
      <div className="container">
        <div className="row">
          <div className="col-12">
            <ul
              className={`nav nav-tabs tabs-${renderingUid}`}
              ref={containerRef}
              role="tablist"
            />
            <div className={`tab-content content-${renderingUid}`}>
              {children}
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Conclusion

This will hopefully provide developers with a starting point for handling placeholders where a client component is required. It should be pretty straightforward to engineer in your handling as appropriate, or to feed the example to AI to help you generate what you need.

Remember, if your AppPlaceholder component doesn't require hooks, you don't need to do any of this extra work. Keep it as simple as possible!