A component that fetches data, transforms it, manages multiple unrelated state slices, and renders three distinct UI panels cannot be tested in isolation, reused elsewhere, or modified safely. ISO 25010 maintainability.modularity and reusability both degrade: a change to the API call shape ripples into the rendering logic and breaks tests that only intended to cover the UI. AI-generated "God components" in app/page.tsx are a common failure mode — the model writes one large coherent component because it never has to maintain it.
Medium because mixed-concern components increase bug surface and slow down every future change, but they do not immediately break production behavior.
Extract data fetching into server components or custom hooks, leaving rendering components purely presentational. For Next.js App Router:
// app/dashboard/page.tsx (server component — fetches)
export default async function DashboardPage() {
const data = await fetchDashboardData()
return <DashboardView data={data} />
}
// components/DashboardView.tsx (client component — renders)
export function DashboardView({ data }: { data: DashboardData }) {
// pure rendering logic only
}
A useful heuristic: if you can't describe what a component does in one sentence without the word "and," split it.
ID: code-maintainability.code-organization.single-responsibility
Severity: medium
What to look for: Examine component files (.tsx, .jsx, .vue, .svelte). Look for signs that a single component is doing too many unrelated things:
fetch() or database queries) mixed with UI renderingapp/page.tsx or layout files that contain hundreds of lines of mixed logicFocus on the 5 largest component files by line count.
Pass criteria: Count all component files in the project and classify each as single-responsibility or mixed-concern. The majority of components (at least 80%) have a single clear responsibility — they either fetch data, transform data, or render UI, not all three. Files over 150 lines are examined and found to have cohesive, related logic. Report the ratio: "X of Y component files follow single-responsibility."
Fail criteria: Two or more large components (over 150 lines) contain mixed concerns: inline data fetching + business logic + rendering with no clear separation. Or a single component handles 3+ unrelated UI sections. Do NOT pass when a component is under 150 lines but combines data fetching, state management, and rendering for unrelated features.
Skip (N/A) when: The project has fewer than 5 component files. Signal: fewer than 5 .tsx/.jsx/.vue/.svelte files.
Detail on fail: Name the offending files and what they mix. Example: "app/page.tsx (320 lines) contains API fetch, authentication logic, and rendering for 4 unrelated sections. components/Dashboard.tsx mixes data fetching, state management, and 6 distinct UI panels."
Remediation: Components that try to do everything become impossible to test, reuse, or modify safely. The rule of thumb: if you can't describe what a component does in one sentence without using "and", it should probably be split.
For a component that fetches + renders: extract the data fetching into a server component or custom hook, and make the rendering component a pure presentational component that receives data as props.
For Next.js specifically:
// Before: one component does everything
// After: split into server component (fetches) + client component (renders)
// app/dashboard/page.tsx (server component — fetches)
export default async function DashboardPage() {
const data = await fetchDashboardData()
return <DashboardView data={data} />
}
// components/DashboardView.tsx (client component — renders)
export function DashboardView({ data }: { data: DashboardData }) {
// pure rendering logic only
}