Refactoring Monolithic Pages
This skill outlines the standard operating procedure for decomposing large React pages (1000+ lines) into clean, maintainable component hierarchies. This process enhances readability, reduces cognitive load, and isolates complexity.
The "Extract & Glue" Protocol
1. Audit & Identify logical Sections
Scan the monolithic file (page.tsx) and identify distinct UI sections. Look for:
- •Visual Boundaries: Cards, Panels, Modals, Lists.
- •Functional Boundaries: Chat interfaces, Status displays, Forms.
- •Comment Blocks: Often developers leave comments like
// Chat Sectionor// Verification Modal. These are natural cut-points.
2. The Extraction Strategy
For each identified section, create a new component file.
A. Analyze Dependencies
Before cutting code, determine what data the section needs:
- •Props: What primitive values (IDs, strings) does it rely on?
- •State: Does it use
useState? Is that state local (only used here) or shared? - •Server Data: Does it rely on
useQuerydata?
B. Component Classification
Decide on the component type:
- •Smart Container: Fetches its own data given an ID. Best for independence.
- •Presentation Component: Purely receives data via props. Best for reusability.
- •Hybrid: Receives some data, manages its own ephemeral UI state.
3. Move Logic, Not Just JSX
Don't just copy the JSX. Move the brain too.
- •Formatting Functions: Move helpers like
formatLabelorgetPhaseinto the component or a shared utility file. - •Event Handlers: Move
handleSave,handleDeletefunctions into the component. - •Effects: Move
useEffectlogic unrelated to the parent into the child.
4. State Management Migration RULES
- •Local UI State: Move
useStateinto the child component. (e.g.,isExpanded,editingId). - •Server State: ideally, pass the
IDand letting the child use the same React Query hook. React Query deduplicates requests, so this is efficient and prevents prop drilling. - •Shared State: If two siblings need to talk, keep the state in the parent or use a lightweight Context/Zustand store (see
client-state-standardsskill).
5. "Glue" The Parent
The original page should become a Layout Orchestrator. Its job is effectively reduced to:
- •Reading URL params.
- •Handling high-level page state (like "loading" or "404").
- •Rendering a clean JSX tree of child components.
Before:
tsx
// page.tsx (1000 lines)
export default function Page() {
const [msg, setMsg] = useState("");
const { data } = useQuery(...);
// ... 500 lines of logic ...
return (
<div>
{/* 200 lines of Chat JSX */}
{/* 200 lines of List JSX */}
</div>
)
}
After:
tsx
// page.tsx (150 lines)
export default function Page() {
const { id } = useParams();
return (
<div className="grid gap-4">
<StatusCard applicationId={id} />
<div className="grid grid-cols-2">
<ChatInterface applicationId={id} />
<TaskRoadmap applicationId={id} />
</div>
</div>
)
}
Validation Checklist
- •Zero Prop Drilling: Are you passing data down 3+ levels? If so, pass the
IDand fetch data lower down. - •Single Responsibility: Does the parent file contain logic that only pertains to one specific child? If yes, move it down.
- •Clean Imports: Does the parent import specific UI libraries (buttons, icons) that are only used in one section? Move them.