For a long time, frontend architecture meant one thing to me: split components into smart and dumb. Smart components fetch data, hold state, call mutations, and pass callbacks down. Dumb components take props and render buttons, forms, tables, and layout.

That split helped. It made my React components smaller and gave me a vocabulary for "this component is doing too much." But as my invoice app grew, a different problem showed up underneath it.

The code was already organized. It had folders for features, forms, shared components, GraphQL queries, and utilities. It was not one giant file. From a distance it looked fine. The trouble was that the rules of the application were scattered. Some lived in buttons. Some in hooks. Some in form submit handlers. Some in shared utilities. Some were only implied by what a backend service method happened to do. The app worked, but I couldn't point at one place and say, "This is what an invoice means here."

That is the architecture problem I care about now.

A note on the code: this describes a refactor I'm in the middle of, not a finished result. The messy snippets are real — taken from the app's code and its git history. The clean snippets are the direction I'm working toward; most of them aren't merged yet, and some don't exist outside this article. Read them as the target, not as a claim about what's on main today.