@partyvelope/react-block-cms
v0.4.0
Published
블록 기반 콘텐츠 CMS — SSR/SEO 안전한 렌더러 + 실시간 미리보기 admin 에디터 (데이터 계층 무관, React).
Maintainers
Readme
@partyvelope/react-block-cms
블록 기반 콘텐츠 CMS 툴킷. SSR/SEO 안전한 렌더러와 실시간 미리보기 admin 에디터를 제공하며, 데이터 계층(백엔드)에 종속되지 않습니다.
- 🧱 블록 타입: hero, rich-text, card-grid, two-column, process-steps, faq, cta, text-panel, callout, table, card-list, info-columns, raw-html, floating-toolbar
- 🖥️ 렌더러는 서버 컴포넌트 안전(hooks 없음) → Next.js App Router / RSC에서 그대로 SSR, 봇 크롤링/SEO OK
- ✏️ 에디터: 블록 추가·순서변경·표시토글·삭제 + 타입별 폼 + 편집 옆 실시간 미리보기 + 토스트 + 삭제 확인
- 🔌 데이터 계층 무관:
onSave(payload)콜백만 연결하면 REST/GraphQL/무엇이든 - 🎨 비개발자용 입력: 제목 강조·줄바꿈·이미지 좌우 위치 등 태그 없이 칸으로 입력
설치
npm i @partyvelope/react-block-cmspeer deps: react >=18, react-dom >=18.
스타일 (둘 중 하나)
A. 컴파일된 CSS (Tailwind 불필요)
import "@partyvelope/react-block-cms/styles.css";B. Tailwind v4 프로젝트 — globals.css:
@import "tailwindcss";
@import "@partyvelope/react-block-cms/theme.css";
@source "../node_modules/@partyvelope/react-block-cms/dist";테마 색상 변경 (어느 방식이든)
:root {
--cms-primary: #16a34a;
--cms-primary-dark: #15803d;
--cms-primary-surface: #f0fdf4;
--cms-primary-light: #dcfce7;
--cms-primary-300: #86efac;
}공개 페이지 렌더링 (서버 컴포넌트)
import { BlockRenderer, type CmsBlock } from "@partyvelope/react-block-cms";
export default async function Page() {
const blocks: CmsBlock[] = await loadBlocksFromAnywhere(); // 당신의 백엔드
return <BlockRenderer blocks={blocks} />;
}Admin 에디터 (클라이언트 컴포넌트)
"use client";
import { PageEditor } from "@partyvelope/react-block-cms/editor";
import type { AdminPage, SavePagePayload } from "@partyvelope/react-block-cms";
export default function Editor({ page }: { page: AdminPage }) {
const handleSave = async (payload: SavePagePayload) => {
await fetch(`/api/pages/${page.slug}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
};
return <PageEditor page={page} onSave={handleSave} />;
}데이터 형태 (최상위)
interface CmsBlock { id: string; type: BlockType; data: BlockDataMap[BlockType]; order?: number; visible?: boolean; }
interface AdminPage { slug: string; displayName: string; theme: { seo?: { title?: string; description?: string } }; blocks: AdminBlock[]; }
interface SavePagePayload { displayName?: string; theme?: {...}; blocks: { type; data; visible }[]; }
type CmsButton = { label: string; href: string; style: "primary" | "outline"; icon?: string };블록의 data는 JSON이므로 DB에 그대로 저장(JSONB 권장). 타입 단일 소스는 src/blocks.ts — 아래는 그 요약이다.
⚠️ [bespoke] 필드 주의: 타입·에디터(BlockForm)로는 지원하지만 번들
BlockRenderer는 렌더하지 않는다. 시안 맞춤 등 소비자 커스텀 렌더러에서 읽어 렌더하는 필드다(번들 렌더러만 쓰면 무시 = 빈 출력). 표시 없는 필드는 번들 렌더러가 렌더한다.
블록별 data 필드
대부분 블록 공통: eyebrow?(윗 라벨), heading(제목), intro?(소개), bg?(섹션 배경).
hero — 상단 배너
title, titleAccent?(강조 줄), subtitle?, subtitle2?, eyebrow?, breadcrumb?, badge?, bg:{type:"color"|"image",value}, bgImage?, theme?:"dark"|"light", tags?:string[], buttons:CmsButton[]
[bespoke] image?:{src,alt} · imageStyle?:"portrait"|"visual"|"wide" · panel?:{title?,items?:string[],note?}(이미지 대신 우측 리스트 카드)
rich-text — 제목+본문(HTML)
eyebrow?, heading?, html(신뢰 입력 → dangerouslySetInnerHTML)
card-grid — 카드 그리드
columns:2|3|4, variant?:"icon"|"number"|"number-lg"|"icon-num", bg?:"white"|"neutral", cards:{icon,num?,title,desc,href?,items?:string[]}[]
[bespoke] gallery?:{src,alt}[](있으면 카드 대신 사진 갤러리)
two-column — 이미지+텍스트
heading, paragraphs:string[], image:{src,alt}, imageSide:"left"|"right", list?:string[], badges?:string[], imageCaption?:{label?,title?}, blob?, listCard?:{title,items,link?}, buttons?
[bespoke] bg? · listCard.note?(작은 주석) · calloutCard?:{title?,text?,buttons?}(우측 강조박스) · note?:{text,style?:"notice"|"legal"}(본문 아래 안내/면책 박스)
text-panel — 텍스트 + 우측 패널
heading, headingAccent?, paragraphs, quote?(인용박스), bg?:"white"|"surface", panelTitle, panelItems?(체크리스트) 또는 panelIcon?+panelText?+panelTone?:"primary"|"warning"(하이라이트 카드)
[bespoke] quoteStyle?:"notice"|"legal"(인용박스 스타일) · buttons?(본문 아래 버튼)
callout — 가운데 강조 밴드
heading, headingAccent?, text?, badges?:string[], theme?:"surface"|"dark"
cta — 강조 배너(버튼)
heading, text?, buttons, theme:"dark"|"light" · [bespoke] bg?
table — 표
heading?, intro?, bg?:"white"|"neutral", dense?, headers:string[], rows:string[][]
card-list — 리스트 카드
columns:2|3, bg?, cards:{num?,title,subtitle?,items:string[]}[]
process-steps — 단계 안내
heading, intro?, steps:{num,title,desc}[]
faq — 자주 묻는 질문
heading?, items:{q,a}[] · [bespoke] note?:{text,style?:"notice"|"legal"}(하단 안내/면책 박스)
info-columns — 2단 정보(시간·지도·연락처)
bg?, left/right: InfoPanel. InfoPanel kind별:
card:title?,icon?,rows?:{label,value,highlight?}[],lines?:{icon?,text,href?}[],note?:{icon?,title?,text},buttons?:{label,href,primary?}[]· [bespoke]table?:{headers?:string[],rows:string[][]}text:eyebrow?,heading?,paragraphs?,buttons?map:title?,addressLines?:string[],sub?,links?:{label,href,primary?}[]
raw-html — 임의 HTML 섹션
bg?:"white"|"neutral"|"surface", html(신뢰 입력)
floating-toolbar — 우하단 플로팅 버튼
items:{icon,href,label,primary?}[]
빌드 / 배포
npm run build # tsup(JS+타입) + tailwind(CSS)
npm publish --access public # 스코프 공개 패키지라이선스
MIT
