# Puck Visual Editor — Implementation Instructions

> **Purpose**: Make an existing Next.js blog homepage **editable** using the Puck visual editor. The existing design is preserved exactly as-is. Existing sections become editable. A library of 38+ additional components becomes available for adding new sections.

> **CRITICAL RULE**: The homepage MUST look **identical** before and after this implementation. You are wrapping existing components — NOT replacing them. If the homepage looks different after implementation, you have failed.

---

## How This Works

There are TWO types of components in the final editor:

1. **Site Components** — The site's existing homepage sections, wrapped to be Puck-editable. These preserve the exact current design.
2. **Library Components** — 38 pre-built components (from `files/components/sections/`) that users can drag in to add NEW sections.

The editor sidebar will show both groups. Users can edit existing sections AND add new ones from the library.

---

## Prerequisites Check

Verify the target site meets ALL requirements:

1. **Next.js 14+** with App Router (`app/` directory)
2. **Tailwind CSS** v3.x configured
3. **Neon PostgreSQL** database with **Drizzle ORM** (`@neondatabase/serverless` + `drizzle-orm`)
4. **Vercel Blob** storage configured (for media uploads)
5. A working `lib/db.ts` (or similar) that exports a Drizzle `db` instance

If any prerequisite is missing, set it up first.

---

## Phase 1: Analyze the Existing Homepage

**This is the most important phase. Read carefully and document everything.**

### 1a. Read the current homepage

Open `app/page.tsx` (or wherever the homepage is rendered). Study every line.

### 1b. Document every section

For EACH visual section/block on the page, document:

```
Section [N]: [Name]
- Component file: components/[path].tsx
- Props it accepts: title, subtitle, image, count, etc.
- Data source: static (hardcoded JSX) | dynamic (DB query) | props from page.tsx
- Current prop values: title="最新ニュース", count={4}, etc.
- Imports from: (any DB utility functions?)
```

### 1c. Identify the page structure

Document what's OUTSIDE the editable content:
- Header component (keep static — NOT editable)
- Footer component (keep static — NOT editable)
- SEO/schema components (keep — preserve all existing SEO)
- Layout wrappers (`<main>`, container divs, className)

These stay as-is. Only the inner content sections become Puck-managed.

---

## Phase 2: Install Dependencies & Copy Library Files

### 2a. Install dependencies

```bash
npm install @puckeditor/core@^0.21.2 lucide-react@^0.564.0 @vercel/blob@^2.4.0
```

Skip any already installed.

### 2b. Copy library files

Copy from the template `files/` directory:

```
files/components/sections/    → <project>/components/sections/
files/components/editor/      → <project>/components/editor/
files/lib/puck/iconMap.ts     → <project>/lib/puck/iconMap.ts
files/lib/puck/parseHelpers.ts → <project>/lib/puck/parseHelpers.ts
files/lib/homepage-content.ts → <project>/lib/homepage-content.ts
files/app/page_editor_admin/  → <project>/app/page_editor_admin/
files/app/api/editor/upload/  → <project>/app/api/editor/upload/
files/app/api/homepage-content/ → <project>/app/api/homepage-content/
```

**DO NOT copy `files/lib/puck/config.tsx`** — you will build a custom config in Phase 3.

**IMPORTANT**: If a file in `components/sections/` already exists in the project with the SAME name, DO NOT overwrite it. The site's existing component takes priority. Only copy library components that don't conflict.

**IMPORTANT**: If there IS a naming conflict, rename the library component (e.g., `LibraryHeroSection.tsx`) and update its references in the config.

---

## Phase 3: Wrap Existing Components for Puck

**See WRAPPING_GUIDE.md for the full pattern with examples.**

For EACH existing homepage section identified in Phase 1:

### 3a. Read the component's props/interface

Open the component file and find its TypeScript interface or prop types:

```typescript
// Example: components/HeroBanner.tsx
interface HeroBannerProps {
  title: string;
  subtitle?: string;
  bgImage?: string;
  ctaText?: string;
  ctaUrl?: string;
}
```

### 3b. Create a Puck config entry

Map each prop to a Puck field type:

| Prop Type | Puck Field Type |
|---|---|
| `string` (short text) | `{ type: "text" }` |
| `string` (long text) | `{ type: "textarea" }` |
| `string` (HTML content) | `{ type: "textarea" }` |
| `string` (image URL) | `{ type: "custom", render: MediaUploadField }` — use `accept="image"` |
| `string` (video URL) | `{ type: "custom", render: MediaUploadField }` — use `accept="video"` |
| `string` (select from options) | `{ type: "select", options: [...] }` |
| `number` | `{ type: "number" }` |
| `boolean` | `{ type: "radio", options: [{ label: "Yes", value: true }, { label: "No", value: false }] }` |
| `array` (list of items) | `{ type: "array", arrayFields: { ... } }` |
| Icon name | `{ type: "custom", render: IconPickerField }` |

### 3c. Handle dynamic (DB-fetching) components

If a component fetches data from the database (e.g., `getLatestArticles()`), it needs TWO versions:

1. **Server version** (existing) — fetches real data, used on the live homepage
2. **Editor version** — shows a placeholder/skeleton in the editor preview

Create a simple editor version:

```typescript
// components/sections/SiteBlogGridEditor.tsx
'use client';
export function SiteBlogGridEditor({ title, count }: { title: string; count: number }) {
  return (
    <section className="py-8">
      <h2 className="text-2xl font-bold mb-4">{title || 'Blog Posts'}</h2>
      <div className="grid grid-cols-2 gap-4">
        {Array.from({ length: count || 4 }).map((_, i) => (
          <div key={i} className="bg-gray-100 rounded-xl h-48 animate-pulse" />
        ))}
      </div>
    </section>
  );
}
```

The config uses the editor version. The server override in page.tsx swaps it with the real version.

### 3d. Add dataMode support (optional but recommended)

For dynamic components, add a `dataMode` field so users can choose:
- `"auto"` — fetch from database (default)
- `"manual"` — enter items manually in the editor

See WRAPPING_GUIDE.md for the full dataMode pattern.

---

## Phase 4: Build the Combined Puck Config

Create `lib/puck/config.tsx` with BOTH site components and library components:

```typescript
// lib/puck/config.tsx
import { Config } from "@puckeditor/core";

// ─── Site component imports ────────────────────────────────────
import { SiteHeroBannerEditor } from "@/components/sections/SiteHeroBannerEditor";
import { SiteBlogGridEditor } from "@/components/sections/SiteBlogGridEditor";
// ... import all wrapped site components (editor versions)

// ─── Library component imports ─────────────────────────────────
// Copy relevant imports from the template config.tsx
import { HeroSection } from "@/components/sections/HeroSection";
import { BannerCTA } from "@/components/sections/BannerCTA";
// ... etc.

// ─── Editor field imports ──────────────────────────────────────
import { MediaUploadField } from "@/components/editor/MediaUploadField";
import { IconPickerField } from "@/components/editor/IconPickerField";

// ─── Helper ────────────────────────────────────────────────────
const stripPuck = (props: any) => {
  const { puck: _, id: __, ...rest } = props;
  return rest;
};

export const puckConfig: Config = {
  categories: {
    // ─── Site's own sections (editable wrappers) ───────────────
    "Site Components": {
      components: [
        "SiteHeroBanner",
        "SiteBlogGrid",
        "SiteCTA",
        // ... all wrapped site components
      ],
    },

    // ─── Library components (for adding NEW sections) ──────────
    "Hero & Banner": {
      components: ["HeroSection", "BannerCTA", "HeroVideoCinematic", "HeroSlideCarousel",
                    "HeroFullWidthSlider", "SplitContentHero", "HeroBlogCarousel", "FeaturedArticleHero"],
    },
    "Content": {
      components: ["IntroSection", "TextParagraph", "SectionHeading", "RichText",
                    "ImageBlock", "VideoEmbed", "Glossary"],
    },
    "Lists & Rankings": {
      components: ["RankedTopList", "ReviewSection", "DetailedServiceCard", "ServiceDetailBlock"],
    },
    "Tables": {
      components: ["DataTable", "ComparisonTable"],
    },
    "Interactive": {
      components: ["ProcessSteps", "StepsTimeline", "FAQ", "ProcessFlow", "CommonProblems"],
    },
    "Cards & Showcase": {
      components: ["CategoryGrid", "HighlightCards", "FeaturesGrid", "TargetAudience", "ArticleCardCarousel"],
    },
    "Promotions & CTA": {
      components: ["CTAButton", "AppPromoSection"],
    },
    "Navigation": {
      components: ["StickyNav", "TableOfContents", "IconNavGrid"],
    },
    "Newsletter": {
      components: ["NewsletterFull", "NewsletterInline"],
    },
    "Author": {
      components: ["AuthorBox", "AuthorProfile"],
    },
    "Layout": {
      components: ["Spacer", "Divider"],
    },
    "Dynamic Content": {
      components: ["BlogGrid", "ArticleTrendingRanking", "ArticleCategorySections",
                    "FeaturedArticleSpotlight", "TwoColumnLayout", "BlogShowcaseGrid"],
    },
  },

  components: {
    // ═══════════════════════════════════════════════════════════════
    // SITE COMPONENTS (wrapped from existing homepage)
    // ═══════════════════════════════════════════════════════════════
    SiteHeroBanner: {
      label: "Hero Banner (Site)",
      fields: {
        title: { type: "text", label: "Title" },
        subtitle: { type: "textarea", label: "Subtitle" },
        bgImage: {
          type: "custom", label: "Background Image",
          render: ({ value, onChange }: any) => (
            <MediaUploadField value={value || ''} onChange={onChange} accept="image" />
          ),
        },
        ctaText: { type: "text", label: "Button Text" },
        ctaUrl: { type: "text", label: "Button URL" },
      },
      defaultProps: {
        title: "EXACT TITLE FROM EXISTING PAGE",  // ← copy from current page
        subtitle: "EXACT SUBTITLE",
        bgImage: "EXACT IMAGE URL",
        ctaText: "EXACT BUTTON TEXT",
        ctaUrl: "EXACT BUTTON URL",
      },
      render: (props: any) => <SiteHeroBannerEditor {...stripPuck(props)} />,
    },

    // ... more site components ...

    // ═══════════════════════════════════════════════════════════════
    // LIBRARY COMPONENTS (from template — copy from files/lib/puck/config.tsx)
    // ═══════════════════════════════════════════════════════════════
    // Copy ALL component configs from the template config.tsx here.
    // They go AFTER the site components.
    // See COMPONENT_REFERENCE.md for the full list.
  },
};
```

### Important rules for the combined config:

1. **Site components go FIRST** in the categories (they appear at the top of the sidebar)
2. **Library components go AFTER** — copy their configs from the template `config.tsx`
3. **defaultProps for site components** MUST contain the EXACT current content from the existing page
4. **Labels are always in English** — field labels, category names, button labels
5. **Content stays in Japanese** — titles, descriptions, all user-facing text
6. **DO NOT duplicate**: If a library component does exactly the same thing as a site component, skip the library version or add it with a different label like "Hero Section (Library)"

---

## Phase 5: Database Setup

### 5a. Add the homepage_content table

Open the project's Drizzle schema file and add:

```typescript
export const homepageContent = pgTable('homepage_content', {
  id: uuid('id').primaryKey().defaultRandom(),
  siteId: varchar('site_id', { length: 255 }).notNull().unique(),
  content: jsonb('content').notNull().default({}),
  updatedAt: timestamp('updated_at').defaultNow(),
  updatedBy: varchar('updated_by', { length: 255 }),
});
```

### 5b. Push to database

```bash
npx drizzle-kit push:pg
```

### 5c. Set SITE_ID

Add to `.env.local`:
```
SITE_ID=<unique-site-name>
```

---

## Phase 6: Wire Up DB Functions

Only implement functions that the site's existing components actually USE.

Check `components/sections/` for imports from `@/lib/utils/db`. See DB_FUNCTIONS.md for the full function list and expected return types.

If the site already has these functions → make sure the import paths match.
If not → implement them using `db-editor-stubs.ts` as a starting point.

---

## Phase 7: Update homepage-content.ts

### 7a. Fix imports

```typescript
import { db } from '@/lib/db';                     // ← match your project's DB import
import { homepageContent } from '@/shared/schema';  // ← match where you added the table
```

### 7b. Build DEFAULT_PUCK_DATA

This MUST reproduce the EXACT current homepage using the wrapped site components:

```typescript
export const DEFAULT_PUCK_DATA: Data = {
  content: [
    {
      type: "SiteHeroBanner",  // ← site component (NOT library component)
      props: {
        id: "hero-1",
        title: "EXACT TEXT FROM CURRENT PAGE",
        subtitle: "EXACT SUBTITLE FROM CURRENT PAGE",
        bgImage: "EXACT IMAGE URL FROM CURRENT PAGE",
        ctaText: "EXACT BUTTON TEXT",
        ctaUrl: "EXACT URL",
      },
    },
    {
      type: "SiteBlogGrid",
      props: {
        id: "blog-1",
        title: "EXACT HEADING",
        count: 4,  // ← same as current page
      },
    },
    // ... one entry per existing section, in order
  ],
  root: { props: {} },
  zones: {},
};
```

**Rules:**
- Use SITE component names (e.g., `SiteHeroBanner`), not library names
- Copy every piece of text character-for-character from the existing page
- Copy every image URL exactly
- Copy every link href exactly
- Sections must be in the same order as the current page

---

## Phase 8: Convert page.tsx to Use Puck Render

### 8a. Create server config with overrides

For dynamic (DB-fetching) site components, create server overrides that swap the editor version with the real server version:

```tsx
const serverConfig = {
  ...puckConfig,
  components: {
    ...puckConfig.components,
    // Override dynamic site components with server versions
    SiteBlogGrid: {
      ...puckConfig.components.SiteBlogGrid,
      render: (props: any) => {
        const { puck: _, ...rest } = props;
        return <SiteBlogGrid {...rest} />;  // ← the real server component
      },
    },
    // Also override library dynamic components (if used)
    BlogGrid: {
      ...puckConfig.components.BlogGrid,
      render: (props: any) => {
        const { puck: _, ...rest } = props;
        return <BlogGrid {...rest} />;
      },
    },
    // ... etc.
  },
};
```

### 8b. Update page.tsx

```tsx
export default async function HomePage() {
  const { puckData } = await getHomepageContent();

  return (
    <>
      {/* KEEP: All existing SEO/schema components */}
      {/* KEEP: Existing header — do NOT put inside Render */}

      <main className="..."> {/* KEEP existing wrapper classes */}
        <Render config={serverConfig as any} data={puckData as any} />
      </main>

      {/* KEEP: Existing footer — do NOT put inside Render */}
    </>
  );
}
```

---

## Phase 9: SEO / AEO / Schema Preservation

**See SEO_REQUIREMENTS.md for the full specification.**

### Mandatory checks:

1. **Preserve all existing SEO** — do NOT remove any existing `<meta>`, schema, canonical, or structured data
2. **Semantic HTML** — all components must use `<section>`, `<article>`, `<nav>`, `<figure>` etc.
3. **Heading hierarchy** — one `<h1>` per page, proper `<h2>` → `<h3>` nesting
4. **Image alt text** — every `<img>` must have descriptive `alt` text
5. **Schema.org JSON-LD** — add to components that support it:
   - FAQ component → `FAQPage` schema
   - Review components → `Review` schema
   - Article grids → `ItemList` schema
   - Author → `Person` schema
6. **Editor page** — must have `noindex, nofollow` (already set in the template layout)
7. **Core Web Vitals** — no layout shift, lazy loading for images below fold
8. **Internal linking** — use Next.js `<Link>` component, proper `rel` attributes

---

## Phase 10: Build & Verify

### 10a. Build

```bash
npx next build
```

Fix any errors. Common issues:
- Import path mismatches (`@/lib/db` etc.)
- Missing DB functions
- TypeScript type mismatches in wrapped components

### 10b. Visual Comparison (CRITICAL)

1. Navigate to `http://localhost:3000`
2. The homepage MUST look **identical** to the original:
   - ✅ Same sections in the same order
   - ✅ Same text (character-for-character)
   - ✅ Same images, colors, spacing, layout
   - ✅ Same responsive behavior

**If it looks different, STOP. Fix DEFAULT_PUCK_DATA and component wrapping until it matches.**

### 10c. Verify the editor

1. Navigate to `http://localhost:3000/page_editor_admin`
2. Verify:
   - ✅ Editor loads without errors
   - ✅ "Site Components" category shows wrapped components
   - ✅ Library categories show additional components
   - ✅ Preview matches the live homepage
   - ✅ Editing fields works (change text, upload images)
   - ✅ Drag & drop reorder works
   - ✅ Adding library components works
   - ✅ Publish saves to DB and updates the live homepage

### 10d. SEO Verification

- ✅ View page source — all text is in the HTML (not client-rendered)
- ✅ Schema.org JSON-LD is present for FAQ, Review, Author components
- ✅ Meta title and description are correct
- ✅ No `noindex` on the homepage (only on the editor page)
- ✅ Heading hierarchy is correct (single h1, proper nesting)

---

## Troubleshooting

### Homepage looks different after implementation
- **Cause**: DEFAULT_PUCK_DATA doesn't match original, or CSS classes don't map
- **Fix**: Re-read the original page.tsx, ensure every prop value is copied exactly

### Editor shows blank preview for a component
- **Cause**: The component is a server component (async) being used in the client-side editor
- **Fix**: Create an editor version (see Phase 3c) that shows a static preview

### Library components look different from site components
- **Expected**: Library components have their own styling. They're meant for adding NEW sections.
- The site's existing sections use the site components (wrapped originals)

### "Module not found" errors
- Check import paths use `@/` aliases
- Verify `tsconfig.json` has `"@/*": ["./*"]` in `paths`

### Dynamic components show no data
- Implement the DB functions (see Phase 6 and DB_FUNCTIONS.md)
- Or switch to "Manual" mode in the editor to enter data manually

### Publish works but homepage doesn't update
- Verify `revalidatePath('/')` in the API route
- Check `SITE_ID` in `.env.local`

---

## Reference Documents

- **WRAPPING_GUIDE.md** — Detailed pattern for wrapping any component with examples
- **COMPONENT_REFERENCE.md** — Full reference for all 38 library components
- **DB_FUNCTIONS.md** — Database function specifications and return types
- **SEO_REQUIREMENTS.md** — SEO, AEO, and Schema.org requirements
