6.3 KiB
name, description, user-invocable
| name | description | user-invocable |
|---|---|---|
| dom-to-jsx | Rules for converting captured DOM fragments into Next.js App Router JSX components. Covers component naming, when to split, Server vs Client classification, hydration-safe patterns, image handling (next/image vs raw img), and prop extraction for repeated patterns. Load this in every generation run from Stage 1 onward. | false |
DOM → JSX
The generate agent receives a DOM fragment per section. Convert it into idiomatic Next.js 14+ App Router JSX.
Component naming
- PascalCase. Section-scoped:
HeroSection,FeatureGrid,TestimonialCard,PricingTable,CTAStrip. - File path:
src/components/sections/<Name>.tsxfor top-level sections,src/components/cards/<Name>.tsxfor repeated card components,src/components/ui/<Name>.tsxfor primitives (button, badge). - Default export matches the file name. Named exports are fine for sub-components.
When to split
Split a component when any of these is true:
- File exceeds 150 lines of JSX
- JSX nesting exceeds 3 levels
- A repeated pattern appears 3+ times within the section
When splitting for a repeat, extract a component with a data prop (array of items) or a single-item prop shape matching the manifest's candidate_component proposal.
Example — manifest says a FeatureCard appears 6 times with {title, description, icon}:
// src/components/cards/FeatureCard.tsx
type Props = { title: string; description: string; icon: string };
export default function FeatureCard({ title, description, icon }: Props) { ... }
// src/components/sections/Features.tsx
import FeatureCard from "@/components/cards/FeatureCard";
const ITEMS = [{ title: "...", description: "...", icon: "..." }, ...];
export default function Features() {
return <div className="grid grid-cols-3 gap-8">{ITEMS.map(i => <FeatureCard key={i.title} {...i} />)}</div>;
}
Extract ITEMS as a local const in the section file for Stage 1. If the data is clearly content-managed (12+ items, or visually heterogeneous), move to src/content/<section>.ts as a typed export.
Server vs Client
Default: Server Component. Add "use client" at the top of a file only if any of these is true:
- Uses
useState,useEffect,useRef, or any other hook - Uses event handlers (
onClick,onChange, etc.) — interactive buttons that do more than<a href>-style navigation - Uses
framer-motion'smotion.*components - Uses
lottie-reactor any other client-only lib - Uses browser APIs (
window,document,localStorage)
Do not mark parents client unnecessarily. Keep "use client" at the leaf — a client leaf can live inside a server parent, not vice versa.
Hydration safety
- No
Date.now()/Math.random()/new Date()at render. If needed, generate inuseEffectand store in state. At render time the first pass must be deterministic. - No reading
window/documentat module scope. Wrap inuseEffector guard withtypeof window !== "undefined". suppressHydrationWarningis a last resort — only on a single element where the server/client divergence is intentional and minimal (e.g. a<time>element).- Do not pass
new Date()or live content into a Server Component from within a client wrapper at first paint — you will get a mismatch.
Image handling
Default: next/image when all of these hold:
- The image has known dimensions (the manifest captured them)
- The image is a raster (jpg, png, webp, avif)
- The image is content, not purely decorative
Example:
import Image from 'next/image';
<Image src="/assets/cloned/images/abc1234-hero.jpg" alt="" width={1920} height={1080} />;
Raw <img> for:
- SVGs (use native
<img src="/assets/cloned/svg/xyz.svg" alt="" />or inline the SVG as a component if <5KB and decorative) - Images with unknown dimensions at clone time
- Decorative blur-ups where LCP is not a concern
Inline SVG when:
- The SVG is <5KB in the source
- The SVG is used in-flow (not as a background-image)
- Animated parts need to be controlled from JS
Configure next.config.js to allow loading from /assets/cloned paths — no external loader needed since everything is already local.
Video
<video src="/assets/cloned/videos/<hash>.mp4" autoPlay loop muted playsInline className="..." />
Always playsInline (iOS won't inline autoplay without it). Always muted for autoplay (browsers require it).
Links
- Internal:
<Link href="/...">fromnext/link. - External:
<a href="..." target="_blank" rel="noopener noreferrer">. - Anchor in page: plain
<a href="#id">is fine —<Link>with a hash works too.
Accessibility
Keep what the source had — do not invent. If the source has aria-label or alt="", preserve it. If the source is inaccessible, the clone is inaccessible. This is a fidelity tool, not a correction tool.
That said — do not remove accessibility attributes the source had.
Third-party widgets (skip per clone-staging)
When the manifest flags a subtree as a third-party widget, replace with:
<div style={{ width: W, height: H }}>{/* CLONE: skipped third-party widget — <vendor> */}</div>
Pull W and H from the manifest's bounding box for the widget's container. This preserves layout so later sections land at the right Y offset.
Forms
The clone is a static marketing-page mirror. Forms should render visually but the onSubmit handler is (e) => e.preventDefault() — no backend wiring.
If the source had client-side validation that is material to the layout (error states, spinners), keep it as a visual-only useState example. Do not call real APIs.
Attribute translation
class→classNamefor→htmlFortabindex→tabIndex- Hyphenated attrs on SVG: most become camelCase (
stroke-width→strokeWidth), butdata-*andaria-*stay hyphenated. style="..."→style={{ ... }}with camelCase keys. Only inline style that is computed / dynamic; static style always goes to Tailwind percss-to-tailwind.
Content
- Copy text verbatim from the DOM fragment. Do not paraphrase. Do not translate. Do not "improve" grammar.
- Preserve whitespace-sensitive content (
<pre>,<code>) exactly. - Keep HTML entities as-is (
—, ,…) but JSX renders them when placed inside string literals — use{"—"}or{"\u00a0"}for non-breaking spaces when adjacency matters.