React & Next.js analysis
Catch React and Next.js bugs that per-file linters cannot see, before the build: wrong client and server directives, colliding routes, dead server actions, and unused or drilled props.
Fallow finds React and Next.js problems that per-file tools (eslint-plugin-react, eslint-plugin-next) cannot see, often before the build fails. It builds a module graph of the whole project, so it can find:
- A
"use client"file that exports a server-only name. - Two route files that resolve to the same URL.
- A prop drilled through three components.
- A server action that no code uses.
React is the largest ecosystem that fallow supports, and Next.js is the framework that fallow parses in the most depth.
This page is the index of the React and Next.js rules. The dead-code explanation page has the full definition of each rule, with severities, suppression syntax, and the cases where the rule abstains.
The Next.js and React rules are gated on dependencies. Each rule runs only when the relevant package (next, or react, react-dom, or preact) is a declared dependency. They never fire on an unrelated project.
React Server Components directive rules
These four rules check the boundary between "use client" and "use server" across the whole project. They run in the same pass as the rest of dead-code analysis, before a build runs, and only when next is a declared dependency.
invalid-client-export
A file with "use client" that also exports a server-only or route-segment-config name. Examples are metadata, generateMetadata, viewport, generateStaticParams, dynamic, revalidate, runtime, the Pages Router data functions, a route HTTP method handler, and the rest of the reserved set.
Next.js rejects these files at build time. Fallow finds them statically and never flags the default export of the component. Default warn.
mixed-client-server-barrel
A barrel file (a file with at least one export ... from re-export) that re-exports both a "use client" module and a server-only module. An import of one name from this barrel can pull the directive context of the other module across the React Server Components boundary. The result is a confusing build or hydration error far from the barrel. A single-file linter cannot detect this pattern, because it spans files.
The check classifies only direct re-export origins and skips type-only re-exports. It also skips a re-export through a webpack inline loader (export * from 'raw-loader!./server'), because the loader forwards its own output, not the module and its directive. It never flags a barrel that only mixes a client module with an ordinary utility. Default warn.
See mixed client/server barrels.
misplaced-directive
A "use client" or "use server" directive below an import (or any other statement), not in the prologue at the top of the file. When a statement comes before the directive, the bundler treats the string as an ordinary expression and ignores it without a message. A file that you wrote as a client component then becomes a server module, and nothing reports an error. To fix it, move the directive above every import. Default warn.
See misplaced directives.
unused-server-action
A Next.js Server Action (an export of a "use server" file) that no code in the project references. There is no import-and-call, no action={fn} binding, and no <form action={fn}>. Dead actions of this kind build up when you refactor a page. eslint-plugin-next checks one file at a time, so it cannot see them.
The rule is a more specific version of unused-export for "use server" files. It uses the fallow reference graph for the whole project, so prop forwarding and wrapped action factories (withAuditLogging(...)) count as real uses. Default warn.
This rule moves some findings from unused-export (default error) to unused-server-action (default warn). A dead action that failed CI as unused-export before is now a warning. To keep CI failing on it, set unused-server-action: "error", and save any baseline snapshot again once.
Next.js App Router route rules
Both route rules only compute paths over your App Router tree, so no heuristic can misfire. They run only when next is a declared dependency.
The rules check each app root separately, based on your workspace package roots. A monorepo with several independent Next apps that share a path is not flagged. Fallow treats the tree as follows:
- Route groups
(name)do not change the URL. - Parallel slots
@namefork the position. - Intercepting markers and private
_foldersare excluded.
route-collision
Two or more App Router route files (a page or a route handler) that resolve to the same URL in one app root. Route groups and parallel slots do not change the URL, so app/(marketing)/about/page.tsx and app/(shop)/about/page.tsx both own /about.
Next.js fails the build here, but it names only one colliding file and stops. Fallow lists every colliding file at once, before the build. Default error, because the rule matches a next build failure: a project with this problem already fails.
See route collisions.
dynamic-segment-name-conflict
Two or more sibling dynamic route segments at the same tree position with different param names ([id] vs [slug], or a catch-all [...x] vs an optional catch-all [[...x]]). When a request reaches that position, Next.js throws "You cannot use different slug names for the same dynamic path" at runtime.
next build does NOT catch this. The build succeeds and CI passes, and then the route crashes on the first request. The fallow static check is the only gate that fails on it, so it defaults to error.
See dynamic segment name conflicts.
React component health
The component-health signals treat React and Preact components as a graph. They show how many places render a component, whether a live component has a dead prop, and whether a component only adds a layer with no function. Three signals are opt-in and default to off. The prop and fan-in signals are on by default.
Component render fan-in
Fan-in is the number of files that import a given file. For a component, it also measures blast radius. A component that many places render has high fan-in, so a change to it affects many files.
The fallow health analysis computes fan-in for every file. It shows fan-in in the hotspot and refactoring-target output, and adds high fan-in to the coupling penalty in the health score.
The health page explains how fallow computes fan-in / fan-out and where the effective threshold comes from.
unused-component-prop (React arm)
A prop that the component destructures from its first parameter and never reads in its body. The component is live and rendered, but it does not use one of its declared props.
unused-component-prop is one rule key for all frameworks. The React and Preact part runs only when react, react-dom, next, or preact is a dependency. It covers inline-destructured literal props. To avoid false positives, it abstains on:
- Rest spread (
{...rest}). - Props forwarded by spread.
- Props passed as a whole object to a hook.
- A
forwardReformemocomponent whose props come from an imported interface. - Exported public-API component props.
Default warn.
prop-drilling (opt-in, default off)
A prop that passes unused through two or more intermediate components before a component uses it. "Passes unused" means that an intermediate component reads the prop only as the root of a child JSX attribute value, and never uses it in another way. For example, <Child userName={user.name} /> passes user straight through. Per-file linters cannot see this signal, because it spans files.
When you enable the rule, it reports one chain for each drilled prop. The chain has the owning source, each pass-through hop, and the consumer, each with file, line, and component name. The rule abstains when it cannot prove a clean pass-through:
- A
{...props}or{...rest}spread. - A
cloneElement. - A component passed as a prop, a render prop, or function-as-children.
- A matching context
*.Providerin the subtree. - A dynamic hop.
Off by default.
To enable it, set prop-drilling: "warn" (or "error") under rules. The enabled rule also adds a small penalty to the health score: one point for each chain, with a maximum of five. When the rule is off, the health score does not change.
See prop drilling.
thin-wrapper (opt-in, default off)
A component whose entire body is return <Child {...props} />. It renders a single child, forwards all props through a spread, and adds no markup, hooks, or logic of its own. Every call site could render the child directly.
The rule abstains on intentional wrappers:
- A
forwardReformemowrapper. - An exported (public-API) component.
- A context-provider wrapper.
- A
cloneElement. - A render prop or function-as-children.
- A wrapper whose body is more than the single spread-forwarded child.
Off by default. To enable it, set thin-wrapper: "warn" (or "error") under rules.
See thin wrapper.
duplicate-prop-shape (opt-in, default off)
Three or more components, in two or more files, that declare an identical set of prop names. This suggests that you extract a shared Props type (or a base component).
To avoid noise, the rule requires all of these:
- An exact match of the full set.
- At least four significant prop names, after it removes common DOM-passthrough names (
id,className,children,style, ...). - At least three components in at least two files.
Two buttons that only share {label, onClick} are never grouped. Off by default. To enable it, set duplicate-prop-shape: "warn" (or "error") under rules.
See duplicate prop shape.
prop-drilling, thin-wrapper, and duplicate-prop-shape all default to off. Enable the ones you want under rules:
{
"rules": {
"prop-drilling": "warn",
"thin-wrapper": "warn",
"duplicate-prop-shape": "warn"
}
}Reachability: what counts as used
Fallow never flags a component or server action that is used correctly. The fallow framework plugins know the conventions that each router uses to reach code, and count them as uses. They cover:
- Next.js App Router: convention exports (
page,layout,loading,error,route, and the metadata files), route segment config,middleware,proxy, andinstrumentationcount as entry points. - Next.js Pages Router: any file under
pages/, and the data functions that Next calls by name (getStaticProps,getServerSideProps,getStaticPaths). - Server-action bindings:
action={fn},<form action={fn}>, plain import-and-call, component-prop forwarding, and wrapped action factories all count as real uses of a"use server"export. - React Router (
@react-router/dev), Remix (@remix-run/node,@remix-run/react, and the other@remix-run/runtime packages), and TanStack Router (@tanstack/react-router,@tanstack/start,@tanstack/react-start): fallow recognizes their route conventions, so it does not flag route modules as unused.
Fallow counts these conventions automatically, so the component and server-action rules fire only on code that nothing uses. Built-in plugins lists the enabler packages and what each plugin extracts. To see the active plugins in your project, run fallow list --plugins.
If fallow does not count a convention, add the file to entry or write a custom plugin. Before you act on a finding, verify it with fallow dead-code --trace <file>:<symbol>.
See also
Definitions, severities, and suppression syntax for every rule on this page.
Set each rule to error, warn, or off, and enable the opt-in component signals.
Auto-detected enabler packages for Next.js, React Router, Remix, and TanStack Router.
Fan-in, blast radius, and the coupling penalty in the health score.