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

推荐订阅源

博客园_首页
Vercel News
Vercel News
月光博客
月光博客
S
SegmentFault 最新的问题
A
About on SuperTechFans
Microsoft Security Blog
Microsoft Security Blog
U
Unit 42
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
B
Blog RSS Feed
Y
Y Combinator Blog
云风的 BLOG
云风的 BLOG
N
Netflix TechBlog - Medium
小众软件
小众软件
WordPress大学
WordPress大学
G
Google Developers Blog
Recent Announcements
Recent Announcements
H
Hackread – Cybersecurity News, Data Breaches, AI and More
P
Proofpoint News Feed
Blog — PlanetScale
Blog — PlanetScale
MongoDB | Blog
MongoDB | Blog
F
Fortinet All Blogs
博客园 - 【当耐特】
I
InfoQ

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!