---
name: payload-lexical-integration
description: แนวทางการรวม Payload CMS Lexical richText content กับ design system components — อธิบายว่าทำไม design skill output กับ Payload content ถึงอยู่คนละ layer และวิธี integrate มันเข้าด้วยกัน
category: software-development
---

# Payload Lexical Integration

## ปัญหา

เวลาใช้ design skill (ui-ux-pro-max) กับ Payload CMS มักเกิดความสับสน:

- Design skill ให้โค้ดแบบไหน?
- Payload Lexical เก็บ content ยังไง?
- ทำไม content ไม่แสดงหลังสร้าง fields เสร็จ?

## สิ่งที่ต้องเข้าใจก่อน

### Two Layers — แยกกันทำ

```
┌─────────────────────────────────────────────────────────┐
│  DESIGN LAYER (ui-ux-pro-max, ckm:design, ckm:ui-styling)│
│  • Component structure (Hero, Card, Navbar)              │
│  • Color tokens, typography, spacing                    │
│  • Animation specs (150-300ms, ease-out)                 │
│  • Layout grid, responsive breakpoints                   │
│  • Interaction states                                    │
│                                                          │
│  Output: React + Tailwind code — "ภาชนะ" ไม่ใช่ "เนื้อหา"│
└─────────────────────────────────────────────────────────┘
                           ↓
┌─────────────────────────────────────────────────────────┐
│  CONTENT LAYER (Payload CMS)                            │
│  • ข้อความ + format (bold, italic, link)               │
│  • Headings (H1-H6)                                     │
│  • Lists, blockquotes, code blocks                      │
│  • Images, links                                        │
│  • Tables                                               │
│                                                          │
│  Output: Lexical JSON — "เนื้อหา" ไม่ใช่ "ภาชนะ"       │
└─────────────────────────────────────────────────────────┘
```

**Design skill สร้าง "ภาชนะ" — Payload สร้าง "เนื้อหา" — ต้องรวมกันตอน render**

---

## ขั้นตอน

```
[1] Design Phase
    ui-ux-pro-max → Component structure, tokens, animations
    Output: Component skeleton (ไม่มี content)
         ↓
[2] Payload Phase
    สร้าง Collections + richText Fields
    Output: Content structure ใน Payload
         ↓
[3] Content Phase
    พิมพ์ content ใน /admin (Lexical visual editor)
    Output: Lexical JSON
         ↓
[4] Integration Phase
    ครอบ Payload content ด้วย Design components
```

---

## Step 1: Payload Collection

กำหนด content fields ตาม section:

```ts
// src/collections/Posts.ts
const Posts: CollectionConfig = {
  slug: 'posts',
  fields: [
    { name: 'title',          type: 'text',     required: true },
    { name: 'slug',            type: 'text',     required: true },
    { name: 'heroContent',    type: 'richText' },   // content สำหรับ Hero
    { name: 'features',       type: 'array',
      fields: [
        { name: 'heading', type: 'text' },
        { name: 'content', type: 'richText' },       // content ในแต่ละ card
      ]
    },
    { name: 'testimonial',    type: 'richText' },
    { name: 'featuredImage',  type: 'upload', relationTo: 'media' },
    { name: 'status',          type: 'select', options: [...], defaultValue: 'draft' },
  ],
}
```

---

## Step 2: สร้าง Payload Helpers

```ts
// src/lib/payload-helpers.ts
import { getPayload } from 'payload'
import config from '@/payload.config'

export async function getPost(slug: string) {
  const p = await getPayload({ config })
  const { docs } = await p.find({
    collection: 'posts',
    where: { slug: { equals: slug } },
    depth: 2,
  })
  return docs[0] ?? null
}

export async function getAllPosts() {
  const p = await getPayload({ config })
  return p.find({
    collection: 'posts',
    where: { status: { equals: 'published' } },
    depth: 1,
  })
}
```

---

## Step 3: Integration — Design Component + RichText

```tsx
// src/app/(frontend)/posts/[slug]/page.tsx
import { getPost } from '@/lib/payload-helpers'
import { RichText } from '@payloadcms/richtext-lexical'

// Design tokens จาก ui-ux-pro-max
const tokens = {
  hero:    'text-5xl md:text-7xl font-bold tracking-tight',
  section: 'py-20 px-6 max-w-7xl mx-auto',
  card:   'rounded-2xl border border-slate-200 p-6 shadow-sm',
  animate: 'animate-fade-in duration-300 ease-out',
}

// Design component ครอบ Payload richText
function HeroSection({ title, content }: { title: string; content: any }) {
  return (
    <section className={`${tokens.section} text-center`}>
      <h1 className={`${tokens.hero} mb-6`}>{title}</h1>
      {content && (
        <div className="max-w-3xl mx-auto">
          {/* Payload content → RichText → design wrapper */}
          <RichText data={content} className="prose prose-lg" />
        </div>
      )}
    </section>
  )
}

function FeatureCard({ heading, content }: { heading: string; content: any }) {
  return (
    <div className={`${tokens.card} ${tokens.animate}`}>
      <h3 className="text-xl font-semibold mb-3">{heading}</h3>
      {content && <RichText data={content} className="prose prose-sm" />}
    </div>
  )
}

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
  if (!post) return <div>Not found</div>

  return (
    <main className="min-h-screen">
      <HeroSection title={post.title} content={post.heroContent} />

      {post.features?.length > 0 && (
        <section className={`${tokens.section} bg-slate-50`}>
          <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
            {post.features.map((f: any, i: number) => (
              <FeatureCard key={i} heading={f.heading} content={f.content} />
            ))}
          </div>
        </section>
      )}
    </main>
  )
}
```

---

## Animation

Animation apply ที่ **wrapper element** ไม่ใช่ที่ content — เพราะ Lexical JSON เก็บแค่ content structure ไม่เก็บ animation metadata

```tsx
// ✅ ถูก — animation ที่ wrapper
<div className="animate-hero-in">
  <RichText data={post.content} />
</div>

// ❌ ผิด — พยายามใส่ animation ใน Lexical JSON
```

Design skill จะให้ animation spec เป็น CSS class — แค่ apply ที่ element ที่ wrap `<RichText>`

---

## Tailwind Typography Setup

```bash
pnpm add @tailwindcss/typography
```

```ts
// tailwind.config.ts
plugins: [require('@tailwindcss/typography')],
```

ใช้ class `prose` กับ `<RichText>`:

```tsx
<RichText data={post.content} className="prose prose-lg max-w-none" />
```

---

## Payload Config: เปิด Lexical Editor

```ts
// payload.config.ts
import { lexicalEditor } from '@payloadcms/richtext-lexical'

export default buildConfig({
  editor: lexicalEditor(),   // ← ต้องมีถึงจะใช้ visual editor ได้
  // ...
})
```

---

## Common Mistakes

### 1. Design skill ให้ hardcode content

Design skill อาจให้แบบนี้:

```tsx
// ❌ สิ่งที่ design skill อาจให้มา
<div className="hero">
  <h1>Welcome to Our Site</h1>   // hardcode
  <p>Amazing content here...</p>  // hardcode
</div>
```

ต้องแปลงเป็น:

```tsx
// ✅
<div className="hero animate-hero-in">
  <h1>{post.title}</h1>
  {post.heroContent && (
    <RichText data={post.heroContent} className="prose" />
  )}
</div>
```

### 2. ลืม lexicalEditor() ใน payload.config

ถ้าไม่มี `editor: lexicalEditor()` → visual editor จะไม่ขึ้น

### 3. ลืม Tailwind typography plugin

ถ้าไม่มี `@tailwindcss/typography` → richText output จะไม่มี styling

---

## สรุป: ใครทำอะไร

| Design Layer ทำ | Payload Layer ทำ | Integration ทำ |
|-----------------|------------------|----------------|
| Component structure | Content storage | ครอบ `RichText` ด้วย design component |
| Color/tokens | richText fields | Apply design tokens กับ Payload output |
| Typography system | Visual editor (/admin) | Style richText output ด้วย prose class |
| Animation specs | Content rendering | Wrap output ด้วย animation classes |
| Layout grid | SEO fields (via plugin) | Layout คงที่ + content จาก Payload |

---

## Related

- `website-creator` — workflow หลักในการสร้างเว็บด้วย Next.js + Payload
- `payload` — Payload CMS skill (fields, hooks, queries, plugins)
