SDK
Components
React 컴포넌트
Components
SDK가 제공하는 React 컴포넌트들입니다.
RichTextContent
Payload CMS의 Lexical 리치 텍스트를 렌더링하는 컴포넌트입니다.
기본 사용법
import { RichTextContent } from '@01.software/sdk'
function Article({ post }) {
return (
<article>
<h1>{post.title}</h1>
<RichTextContent data={post.content} />
</article>
)
}Props
interface RichTextContentProps {
// 필수: Lexical 에디터 데이터
data: any
// 선택: CSS 클래스
className?: string
// 선택: 내부 문서 링크 변환 함수
internalDocToHref?: (args: {
relationTo: string
doc: any
}) => string
// 선택: 커스텀 블록 컴포넌트
blocks?: {
[blockType: string]: React.ComponentType<any>
}
}스타일링
Tailwind Typography를 사용하면 쉽게 스타일링할 수 있습니다.
import { RichTextContent } from '@01.software/sdk'
function Article({ post }) {
return (
<article className="prose prose-lg max-w-none">
<RichTextContent
data={post.content}
className="prose-headings:font-bold prose-a:text-blue-600"
/>
</article>
)
}prose 클래스는 Tailwind Typography 플러그인에서 제공됩니다.
내부 링크 처리
문서 간 링크를 Next.js 라우팅으로 변환할 수 있습니다.
import { RichTextContent } from '@01.software/sdk'
import Link from 'next/link'
function Article({ post }) {
return (
<RichTextContent
data={post.content}
internalDocToHref={(args) => {
// relationTo에 따라 다른 URL 패턴 적용
switch (args.relationTo) {
case 'posts':
return `/blog/${args.doc.slug}`
case 'products':
return `/products/${args.doc.slug}`
case 'pages':
return `/${args.doc.slug}`
default:
return '/'
}
}}
/>
)
}커스텀 블록
Lexical 에디터의 커스텀 블록을 렌더링할 수 있습니다.
import { RichTextContent } from '@01.software/sdk'
// 커스텀 블록 컴포넌트
function IframeBlock({ url, height }: { url: string; height?: number }) {
return (
<div className="my-8">
<iframe
src={url}
height={height || 400}
className="w-full border-0"
allowFullScreen
/>
</div>
)
}
function VideoPlayerBlock({ videoId }: { videoId: string }) {
return (
<div className="my-8 aspect-video">
<ReactPlayer
url={`https://youtube.com/watch?v=${videoId}`}
width="100%"
height="100%"
controls
/>
</div>
)
}
function CodeBlock({ code, language }: { code: string; language: string }) {
return (
<pre className="my-8 overflow-x-auto rounded-lg bg-gray-900 p-4">
<code className={`language-${language}`}>{code}</code>
</pre>
)
}
// 사용
function Article({ post }) {
return (
<RichTextContent
data={post.content}
blocks={{
Iframe: IframeBlock,
Player: VideoPlayerBlock,
Code: CodeBlock,
}}
/>
)
}지원하는 노드 타입
RichTextContent는 다음 Lexical 노드들을 지원합니다:
텍스트 노드
- Paragraph: 일반 단락
- Heading: H1-H6 제목
- Quote: 인용문
- List: 순서/비순서 목록
인라인 노드
- Link: 링크
- Bold: 굵게
- Italic: 기울임
- Underline: 밑줄
- Strikethrough: 취소선
- Code: 인라인 코드
블록 노드
- Upload: 이미지 업로드
- Relationship: 문서 관계
- Custom Blocks: 커스텀 블록
실전 예제
블로그 포스트
'use client'
import { client } from '@/lib/client'
import { RichTextContent } from '@01.software/sdk'
export default function BlogPostPage({ params }: { params: { slug: string } }) {
const { data: post, isLoading } = client.query.useCollectionSingle('posts', {
where: { slug: { equals: params.slug } }
})
if (isLoading) return <div>Loading...</div>
if (!post) return <div>Post not found</div>
return (
<article className="max-w-4xl mx-auto px-4 py-8">
<header className="mb-8">
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<div className="text-gray-600">
{new Date(post.createdAt).toLocaleDateString()}
</div>
</header>
<div className="prose prose-lg max-w-none">
<RichTextContent
data={post.content}
internalDocToHref={(args) => {
if (args.relationTo === 'posts') {
return `/blog/${args.doc.slug}`
}
return '/'
}}
/>
</div>
</article>
)
}상품 설명
import { RichTextContent } from '@01.software/sdk'
function ProductDescription({ product }) {
return (
<div className="bg-white rounded-lg p-6">
<h2 className="text-2xl font-bold mb-4">상품 설명</h2>
<div className="prose max-w-none">
<RichTextContent
data={product.description}
className="text-gray-700"
/>
</div>
</div>
)
}문서 뷰어
import { RichTextContent } from '@01.software/sdk'
import ReactPlayer from 'react-player'
// 비디오 블록
function VideoBlock({ url }: { url: string }) {
return (
<div className="my-8">
<ReactPlayer
url={url}
width="100%"
height="100%"
controls
className="aspect-video"
/>
</div>
)
}
// 캘아웃 블록
function CalloutBlock({
type,
content
}: {
type: 'info' | 'warning' | 'error'
content: string
}) {
const styles = {
info: 'bg-blue-50 border-blue-500 text-blue-900',
warning: 'bg-yellow-50 border-yellow-500 text-yellow-900',
error: 'bg-red-50 border-red-500 text-red-900',
}
return (
<div className={`my-4 border-l-4 p-4 ${styles[type]}`}>
{content}
</div>
)
}
function DocumentViewer({ document }) {
return (
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-6">{document.title}</h1>
<RichTextContent
data={document.content}
className="prose prose-lg max-w-none"
blocks={{
Video: VideoBlock,
Callout: CalloutBlock,
}}
internalDocToHref={(args) => {
if (args.relationTo === 'documents') {
return `/docs/${args.doc.slug}`
}
return '/'
}}
/>
</div>
)
}이미지 최적화
Next.js Image 컴포넌트와 함께 사용하여 이미지를 최적화할 수 있습니다.
import { RichTextContent } from '@01.software/sdk'
import Image from 'next/image'
// 커스텀 이미지 블록
function OptimizedImageBlock({
src,
alt,
width,
height
}: {
src: string
alt: string
width?: number
height?: number
}) {
return (
<div className="my-8">
<Image
src={src}
alt={alt}
width={width || 1200}
height={height || 800}
className="rounded-lg"
priority={false}
/>
</div>
)
}
function Article({ post }) {
return (
<RichTextContent
data={post.content}
blocks={{
Upload: OptimizedImageBlock,
}}
/>
)
}다크 모드 지원
import { RichTextContent } from '@01.software/sdk'
function Article({ post }) {
return (
<div className="prose dark:prose-invert">
<RichTextContent
data={post.content}
className="
prose-headings:text-gray-900 dark:prose-headings:text-gray-100
prose-p:text-gray-700 dark:prose-p:text-gray-300
prose-a:text-blue-600 dark:prose-a:text-blue-400
"
/>
</div>
)
}반응형 디자인
import { RichTextContent } from '@01.software/sdk'
function Article({ post }) {
return (
<div className="
prose
prose-sm md:prose-base lg:prose-lg
max-w-none
px-4 md:px-8 lg:px-12
">
<RichTextContent data={post.content} />
</div>
)
}성능 최적화
코드 스플리팅
무거운 블록 컴포넌트는 동적 import로 최적화할 수 있습니다.
import { RichTextContent } from '@01.software/sdk'
import dynamic from 'next/dynamic'
// 동적 import로 코드 스플리팅
const VideoPlayer = dynamic(() => import('./VideoPlayer'), {
loading: () => <div>Loading player...</div>,
ssr: false,
})
const CodeEditor = dynamic(() => import('./CodeEditor'), {
loading: () => <div>Loading editor...</div>,
})
function Article({ post }) {
return (
<RichTextContent
data={post.content}
blocks={{
Video: VideoPlayer,
Code: CodeEditor,
}}
/>
)
}메모이제이션
import { RichTextContent } from '@01.software/sdk'
import { memo } from 'react'
const MemoizedRichTextContent = memo(RichTextContent)
function Article({ post }) {
return <MemoizedRichTextContent data={post.content} />
}다음 단계
- Webhooks - Webhook 처리
- Error Handling - 에러 처리
- Advanced - 고급 기능