Tips & Tricks (अपडेट: 2/6/2026)

Claude Code से Rich Text Editor बनाना: Tiptap और React गाइड

Claude Code, Tiptap और React से toolbar, JSON/HTML save और सुरक्षित sanitization वाला rich text editor बनाएं।

Claude Code से Rich Text Editor बनाना: Tiptap और React गाइड

Rich text editor देखने में सिर्फ एक बेहतर input field लगता है, लेकिन production में यह content, security और SEO का हिस्सा बन जाता है। आपको तय करना पड़ता है कि HTML save करना है या JSON, paste किया हुआ content कैसे साफ होगा, unsafe links कैसे रुकेंगे, image URL कैसे validate होंगे, mobile keyboard में selection कैसा चलेगा, और public page पर वही content सुरक्षित तरीके से कैसे render होगा।

इस guide में हम Claude Code को साफ boundaries देते हैं और Tiptap चुनते हैं। Tiptap की official React install docs @tiptap/react, @tiptap/pm और @tiptap/starter-kit install करने को कहती हैं। StarterKit paragraphs, headings, bold, italic और lists जैसे common features देता है। Lexical भी अच्छा framework है; official Lexical site इसे lightweight और modular text editor framework बताती है। लेकिन beginner-friendly CMS editor के लिए Tiptap जल्दी stable baseline देता है।

Claude Code को सही brief दें

Claude Code की official docs बताती हैं कि यह codebase पढ़ सकता है, files edit कर सकता है और commands चला सकता है। इसलिए vague prompt न दें। Feature, file सीमा, save format और security rule साफ लिखें।

Tiptap, React और TypeScript से rich text editor बनाएं।
सिर्फ src/components/RichTextEditor.tsx बदलें।
Bold, italic, H2/H3, lists, links, image URL, JSON output और sanitized HTML output support करें।
Link और image URL में सिर्फ http/https allow करें।
HTML preview और save से पहले DOMPurify use करें।
अंत में manual test checklist दें।

अगर project में Claude Code workflow नया है, पहले Claude Code getting started guide पढ़ें। API या form submit जुड़ा है तो form validation guide भी देखें। Markdown import/export चाहिए तो Markdown processing guide के साथ अलग conversion layer बनाएं।

Install command

पहला version छोटा रखें। Collaboration, comments, mentions और slash menu बाद में जोड़ें, क्योंकि उनसे schema, permission और migration complexity बढ़ती है।

npm install @tiptap/react @tiptap/pm @tiptap/starter-kit @tiptap/extension-link @tiptap/extension-image @tiptap/extension-character-count dompurify

Copy-paste React/TypeScript component

यह component toolbar, URL validation, character count, JSON output, sanitized HTML और local draft save करता है। Tailwind classes सिर्फ styling हैं; आप अपने CSS से replace कर सकते हैं।

"use client";

import type { Editor, JSONContent } from "@tiptap/core";
import { EditorContent, useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import Link from "@tiptap/extension-link";
import Image from "@tiptap/extension-image";
import CharacterCount from "@tiptap/extension-character-count";
import DOMPurify from "dompurify";
import type { ReactNode } from "react";
import { useState } from "react";

export type SavedEditorContent = {
  json: JSONContent;
  html: string;
  plainText: string;
};

type Props = {
  initialContent?: JSONContent | string;
  maxCharacters?: number;
  onChange?: (content: SavedEditorContent) => void;
};

const allowedTags = ["p", "br", "strong", "em", "s", "h2", "h3", "ul", "ol", "li", "blockquote", "code", "pre", "a", "img"];
const allowedAttrs = ["href", "src", "alt", "title", "target", "rel"];

function normalizeHttpUrl(value: string): string | null {
  const trimmed = value.trim();
  if (!trimmed) return null;

  for (const candidate of [trimmed, `https://${trimmed}`]) {
    try {
      const url = new URL(candidate);
      if (url.protocol === "http:" || url.protocol === "https:") return url.toString();
    } catch {
      // Try the next candidate.
    }
  }

  return null;
}

export function sanitizeEditorHtml(html: string): string {
  return DOMPurify.sanitize(html, {
    ALLOWED_TAGS: allowedTags,
    ALLOWED_ATTR: allowedAttrs,
    ALLOW_DATA_ATTR: false,
  });
}

function buildPayload(editor: Editor): SavedEditorContent {
  return {
    json: editor.getJSON(),
    html: sanitizeEditorHtml(editor.getHTML()),
    plainText: editor.getText(),
  };
}

export function RichTextEditor({
  initialContent = "<p>Start writing...</p>",
  maxCharacters = 8000,
  onChange,
}: Props) {
  const [lastSaved, setLastSaved] = useState<SavedEditorContent | null>(null);

  const editor = useEditor({
    extensions: [
      StarterKit.configure({ heading: { levels: [2, 3] } }),
      Link.configure({
        openOnClick: false,
        autolink: true,
        HTMLAttributes: { rel: "noopener noreferrer nofollow", target: "_blank" },
      }),
      Image.configure({ allowBase64: false }),
      CharacterCount.configure({ limit: maxCharacters }),
    ],
    content: initialContent,
    immediatelyRender: false,
    editorProps: {
      attributes: {
        class: "min-h-[260px] rounded-b-md border border-t-0 border-slate-300 bg-white p-4 leading-7 outline-none focus:ring-2 focus:ring-sky-500",
        "aria-label": "Rich text body",
      },
      transformPastedHTML(html) {
        return sanitizeEditorHtml(html);
      },
    },
    onUpdate({ editor }) {
      const payload = buildPayload(editor);
      setLastSaved(payload);
      onChange?.(payload);
    },
  });

  if (!editor) return null;

  const characters = editor.storage.characterCount.characters();
  const saveDraft = () => {
    const payload = buildPayload(editor);
    setLastSaved(payload);
    onChange?.(payload);
    window.localStorage.setItem("article-draft", JSON.stringify(payload));
  };

  return (
    <section className="rounded-md border border-slate-300 bg-slate-50">
      <div className="flex flex-wrap gap-1 border-b bg-white p-2" role="toolbar" aria-label="Formatting toolbar">
        <ToolButton active={editor.isActive("bold")} onClick={() => editor.chain().focus().toggleBold().run()}>B</ToolButton>
        <ToolButton active={editor.isActive("italic")} onClick={() => editor.chain().focus().toggleItalic().run()}>I</ToolButton>
        <ToolButton active={editor.isActive("heading", { level: 2 })} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}>H2</ToolButton>
        <ToolButton active={editor.isActive("heading", { level: 3 })} onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}>H3</ToolButton>
        <ToolButton active={editor.isActive("bulletList")} onClick={() => editor.chain().focus().toggleBulletList().run()}>List</ToolButton>
        <ToolButton active={editor.isActive("orderedList")} onClick={() => editor.chain().focus().toggleOrderedList().run()}>1.</ToolButton>
        <ToolButton onClick={() => {
          const input = window.prompt("Link URL", "https://");
          const href = input ? normalizeHttpUrl(input) : null;
          if (!href) return window.alert("Use an http or https URL.");
          editor.chain().focus().extendMarkRange("link").setLink({ href }).run();
        }}>Link</ToolButton>
        <ToolButton onClick={() => {
          const input = window.prompt("Image URL", "https://");
          const src = input ? normalizeHttpUrl(input) : null;
          if (!src) return window.alert("Use an http or https image URL.");
          editor.chain().focus().setImage({ src, alt: "" }).run();
        }}>Image</ToolButton>
      </div>
      <EditorContent editor={editor} />
      <footer className="flex items-center justify-between gap-3 border-t p-3 text-sm">
        <span>{characters}/{maxCharacters} characters</span>
        <button type="button" onClick={saveDraft} className="rounded bg-slate-900 px-3 py-2 text-white">Save draft</button>
        {lastSaved && <span>HTML: {lastSaved.html.length} bytes</span>}
      </footer>
    </section>
  );
}

function ToolButton({ active, onClick, children }: { active?: boolean; onClick: () => void; children: ReactNode }) {
  return (
    <button type="button" aria-pressed={active} onClick={onClick} className={active ? "rounded border bg-sky-100 px-2 py-1" : "rounded border bg-white px-2 py-1"}>
      {children}
    </button>
  );
}

JSON और HTML save/load

शुरुआत में तीन outputs save करें: JSON re-editing के लिए, sanitized HTML public rendering के लिए, और plainText search, RSS, meta description और length check के लिए। Tiptap की JSON/HTML output guide भी बताती है कि JSON या HTML format अपने आप secure नहीं है; user input validate करना जरूरी है।

import { useEffect, useState } from "react";
import { RichTextEditor, type SavedEditorContent } from "./RichTextEditor";

export function ArticleEditorPage() {
  const [draft, setDraft] = useState<SavedEditorContent | null>(null);

  useEffect(() => {
    const raw = window.localStorage.getItem("article-draft");
    if (raw) setDraft(JSON.parse(raw) as SavedEditorContent);
  }, []);

  return (
    <main>
      <RichTextEditor initialContent={draft?.json ?? "<p>New article</p>"} onChange={setDraft} />
      <article dangerouslySetInnerHTML={{ __html: draft?.html ?? "" }} />
    </main>
  );
}

Security review

DOMPurify के official repository में इसे HTML, MathML और SVG के लिए XSS sanitizer कहा गया है। फिर भी server side पर user, permission, HTML size, plainText size, allowed tags, URL scheme और image domain दोबारा check करें। Client sanitization सिर्फ एक layer है।

flowchart LR
  A["Editor UI"] --> B["Tiptap JSON"]
  A --> C["Sanitized HTML"]
  B --> D["Database draft"]
  C --> D
  D --> E["Public page"]
  E --> F["Search / RSS / CTA"]

Claude Code से implementation के बाद दूसरा review pass मांगें। उसे javascript: link, data: image, rich paste, large payload, mobile selection, keyboard focus, save/load और public renderer check करने को कहें।

Use cases, pitfalls और CTA

पहला use case blog CMS है। JSON से re-edit करें, HTML से publish करें, plainText से SEO और search बनाएं। दूसरा internal knowledge base है, जहां headings, lists, code blocks और links काफी होते हैं। तीसरा ecommerce product description है, जहां HTML और image limits जरूरी हैं। चौथा AI draft editor है। AI-generated HTML भी untrusted input है।

Common pitfalls: सिर्फ HTML save करना, server validation छोड़ना, mobile test टालना, और पहले version में tables, embeds, mentions या collaboration जोड़ देना। पहले basic edit-save-preview सुरक्षित करें।

Rich text editor content revenue को तेज करता है: SEO articles, product pages, training material और CTA जल्दी update होते हैं। Process बनाना हो तो Claude Code products and templates देखें; team rollout या implementation help चाहिए तो Claude Code training and consultation देखें।

Practical test में सबसे अच्छा result तब मिला जब toolbar से पहले payload design तय किया गया: JSON, sanitized HTML, plainText और URL validation। Publish से पहले formatted paste, javascript: link, long text, mobile width, save, reload और public render जरूर जांचें।

#Claude Code #Rich Text Editor #React #Tiptap #Content Management
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.