원클릭으로
mandu-hydration
Island import 규칙. 반드시 @mandujs/core/client에서 import. "use client", .island.tsx, useState, hydration 작업 시 자동 호출.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Island import 규칙. 반드시 @mandujs/core/client에서 import. "use client", .island.tsx, useState, hydration 작업 시 자동 호출.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Canonical Mandu agent workflow. Use first in Mandu projects before direct source edits so Codex, Claude Code, Gemini CLI, and other agents follow the same context -> plan -> apply -> verify -> repair loop.
React composition patterns for Mandu applications. Use when designing Island components, managing shared state, or building reusable component APIs. Triggers on compound components, context providers, boolean props, or component architecture tasks.
Production deployment patterns for Mandu applications
File-system based routing for Mandu. Use when creating pages, API routes, layouts, or dynamic routes. Triggers on tasks involving app/ folder, page.tsx, route.ts, layout.tsx, [id], [...slug], or URL patterns.
Architecture guard system for Mandu. Use when checking layer dependencies, enforcing architecture rules, or validating file locations. Triggers on tasks involving architecture, layers, dependencies, or guard commands.
Island Hydration pattern for Mandu. Use when creating interactive components, client-side state, or partial hydration. Triggers on tasks involving "use client", client.tsx, useState, useEffect, Island, or hydration.
| name | mandu-hydration |
| description | Island import 규칙. 반드시 @mandujs/core/client에서 import. "use client", .island.tsx, useState, hydration 작업 시 자동 호출. |
Island Hydration은 페이지의 일부분만 클라이언트에서 인터랙티브하게 만드는 기술입니다.
Client islands MUST import from @mandujs/core/client, NOT @mandujs/core.
The main module includes server-side dependencies that break client bundles.
// CORRECT
import { island } from "@mandujs/core/client";
import { useServerData, useHydrated, useIslandEvent } from "@mandujs/core/client";
// WRONG - will cause build errors or bloated bundles
import { island } from "@mandujs/core";
| File | Purpose |
|---|---|
*.island.tsx | Island component (auto-detected for hydration) |
*.client.tsx | Client-only logic file |
*.slot.ts / *.slot.tsx | Server-side data loader |
All island files MUST have "use client" directive at the very top.
island() takes a single definition object. Do not call
island("visible", Component); that is not a supported runtime API.
// app/counter.island.tsx
"use client";
import { wrapComponent } from "@mandujs/core/client";
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
export default wrapComponent(Counter);
// app/chat.island.tsx
"use client";
import { island } from "@mandujs/core/client";
import { useState } from "react";
export default island<ServerData>({
setup(data) {
const [messages, setMessages] = useState(data.initialMessages);
return { messages, setMessages };
},
render({ messages, setMessages }) {
return <div>{messages.map(m => <p key={m.id}>{m.text}</p>)}</div>;
}
});
Islands are page-level client bundles. They are discovered from route/client
modules and rendered by the framework wrapper, not embedded as inline JSX. If
you need an interactive region inside a server page, use partial() and render
its .Render component.
"use client";
import { partial } from "@mandujs/core/client";
const CounterPartial = partial({
component: Counter,
priority: "visible",
});
export function Header() {
return <CounterPartial.Render initialCount={0} />;
}
| Priority | When loaded | Use case |
|---|---|---|
"immediate" | Page load | Critical interactions (nav, auth) |
"visible" | In viewport | Default - most components |
"idle" | Browser idle | Below-fold content |
"interaction" | User interacts | Heavy widgets (editor, map) |
// app/page.tsx or manifest route config
export const hydration = {
strategy: "island",
priority: "visible",
preload: false,
};
// Inline client regions use partial({ priority }).
partial({ component: CommentSection, priority: "visible" });
import { useServerData, useHydrated, useIslandEvent } from "@mandujs/core/client";
// Access data from server slot
const data = useServerData<UserData>("user", defaultValue);
// Check if component has hydrated (SSR-safe code branching)
const isHydrated = useHydrated();
// Cross-island communication
const { emit, on } = useIslandEvent();
emit("cart:updated", { items: newItems });
on("cart:updated", (data) => setCartItems(data.items));
// spec/slots/user-profile.slot.ts (server)
import { Mandu } from "@mandujs/core";
export default Mandu.filling()
.get(async (ctx) => {
const user = await db.getUser(ctx.params.id);
return ctx.ok({ user }); // Data available to Islands
});
// app/user-profile.island.tsx (client)
"use client";
import { useServerData } from "@mandujs/core/client";
export default function UserProfile() {
const { user } = useServerData("user-profile");
return <div>{user.name}</div>;
}
@mandujs/core instead of @mandujs/core/client in Islands"use client" directive in island filesisland("visible", Component) instead of island({ setup, render }) or wrapComponent(Component)<MyIsland />; use partial() for inline client regionsuseState/useEffect in server components (non-island files)"immediate" priority (defeats partial hydration).island.tsx files (increases bundle size)mandu-mcp-verify — island 편집 후 mandu_doctor (import/location 진단) drill-down