JSON UI에서 먼저 부딪힌 문제
서버가 JSON으로 화면 구조를 내려주면 React 컴포넌트로 렌더링하는 라이브러리다. 대시보드, 동적 폼, CMS처럼 배포 없이 구성을 바꿔야 하는 화면에 쓸 수 있다.
처음에는 프로젝트마다 재귀 렌더러와 상태 관리를 따로 만들었다. 이 작업을 반복하지 않으려고 공통 부분을 패키지로 분리했다.
JSON 문서 → 정규화된 store → React renderer → 노드별 업데이트

같은 문서를 Document, Nodes, Store 관점에서 확인하는 Storybook inspector.
상태를 노드 단위로 나누기
문서를 ID 기준의 entity map으로 정규화했다. 컴포넌트는 자기 노드만 구독하고, updateNodeState(id)가 호출되면 해당 ID의 구독자에게만 알린다. 서버에서 들어온 값은 각 컴포넌트의 Zod schema로 확인한다.
Toggle 하나가 바뀔 때
"use client";
import {
useSduiLayoutAction,
useSduiNodeSubscription,
} from "@lodado/sdui-template";
import { z } from "zod";
const toggleSchema = z.object({ checked: z.boolean() });
function Toggle({ id }: { id: string }) {
const { state } = useSduiNodeSubscription({
nodeId: id,
schema: toggleSchema,
});
const store = useSduiLayoutAction();
return (
<button
onClick={() =>
store.updateNodeState(id, { checked: !state.checked })
}
>
{state.checked ? "ON" : "OFF"}
</button>
);
}
버튼을 누르면 전체 문서가 아니라 이 Toggle을 구독한 컴포넌트만 다시 렌더링된다.
설계 철학
서버는 구조를, 클라이언트는 상호작용을 맡는다
서버는 어떤 컴포넌트를 어떤 순서로 보여줄지 정한다. 클릭과 입력 같은 상호작용은 React 컴포넌트가 처리한다. JSON이 React의 모든 동작까지 흉내 내기 시작하면 오히려 수정하기 어려워진다.
문서 전체보다 바뀐 노드를 본다
트리 형태의 JSON을 그대로 상태로 들고 있지 않고 nodes map과 rootId로 정규화한다. 노드 조회는 ID로 끝내고, 변경 알림도 그 ID의 구독자에게만 보낸다. 큰 문서에서도 업데이트 범위를 예상하기 쉬워진다.
서버 JSON은 신뢰하지 않는다
TypeScript 타입은 런타임 입력을 막아 주지 못한다. custom component가 자기 Zod schema로 state를 확인하게 한 이유다. 잘못된 JSON이 화면 깊숙한 곳에서 예외를 내기 전에 컴포넌트 경계에서 잡는다.
관계와 예외를 숨기지 않는다
다른 노드 상태가 필요하면 reference로 관계를 적는다. 기본 컴포넌트가 맞지 않을 때는 node ID나 type 단위 override를 쓴다. 암묵적인 전역 상태나 renderer 수정으로 우회하지 않으려 했다.
모든 화면을 SDUI로 만들지는 않는다
서버가 구조를 바꿀 이유가 없는 정적 화면은 평범한 React가 더 단순하다. SDUI는 대시보드 구성, 동적 폼, CMS처럼 서버 제어가 실제 요구사항일 때만 쓴다.
현재 npm 패키지로 공개해 다른 프로젝트에서도 사용하고 있다.