Sep 08, 2025·7 min read

Feature folders that survive product changes in frontend

Feature folders help teams organize frontend code around user jobs, ship changes with less churn, and avoid painful rewrites as products grow.

Feature folders that survive product changes in frontend

Why folder structures start to hurt

A folder structure feels fine when a product is small. Pages live in one place, components in another, hooks somewhere else, and the repo looks tidy. Then the product grows, and one user flow starts touching half the codebase.

Take a simple checkout change. Product wants to add a company name field for business buyers. It sounds small, but the work spreads across a page file, two form components, a validation helper, a cart hook, and an API mapper. To the user, it is one change. In the code, it turns into five separate jobs.

That mismatch slows teams down. Developers stop thinking about the customer flow and start thinking about folder rules. Where should this logic live? Which shared component should they reuse? Does it belong with the page, the hook, or some common utils file? Those questions add friction, and the friction gets worse over time.

New teammates usually feel it first. Instead of learning how the product works, they learn the repo's filing system. They memorize where form hooks go, where API helpers go, and which shared folder hides old logic nobody wants to touch. That is a bad trade. People should understand the signup flow or the billing flow before they need a map of naming rules.

Shared code often becomes the real mess. A button or date helper fits in a common folder. Business logic usually does not. Once teams move logic into "shared" because two features use it, that folder starts collecting random pieces from everywhere. A tax rule sits next to a search formatter. A cart helper lives beside profile code. Nothing feels clearly owned, so every change feels risky.

Feature folders start to make sense when a team reaches that point. They match code to the job a user is trying to do. When a product change lands, the team can stay close to one feature instead of hunting across the whole repo. That will not remove every messy edge, but it cuts a lot of wasted movement.

Start with the user's job

Most product changes begin with a user task. A team adds guest checkout, changes billing, or shortens sign up. If the codebase is split into components, hooks, api, and tests, one small product change can spread across the repo.

A better starting point is the job the user is trying to finish. Name folders after that job: sign-up, checkout, billing, invite-team, reset-password. Those names match the language product managers, designers, and support teams already use. That alone removes a lot of translation.

This is why feature folders usually age better than file-type folders. When a team talks about checkout, everyone should be able to open one place and see the screen, the form rules, the API calls, and the tests tied to checkout.

Keep the parts of that job close together. In most cases, one feature folder should hold the UI for that task, its state and form logic, the API requests it uses, and the tests around it.

That does not mean every file must stay inside the folder forever. Shared code still has a place. But task-specific code should stay with the task until more than one feature really needs it.

A simple structure might look like this:

features/
  checkout/
    components/
    hooks/
    api/
    state/
    tests/

This layout helps when the product changes. If the business adds "buy now" next week, the team can update checkout without digging through global folders and guessing what else might break.

Naming matters more than people think. Use product words, not technical labels. billing is better than payment-module. sign-up is better than user-onboarding-ui unless those two jobs are actually different in the product.

For startups and busy product teams, the payoff is simple: fewer files to search, fewer accidental edits, and fewer rewrites when the product shifts. When the folder name matches the user's goal, the code usually stays easier to change.

Pick the feature boundaries

Most folder structures go wrong in the same place. Teams group code by screen, component type, or framework rule before they decide what each part of the product is trying to help the user do. It looks neat for a while. Then one product change touches five folders.

A better place to start is a single page with the main user flows drawn on it. Keep it plain. Write the user goal, the first action, the steps in the middle, and the result they expect at the end.

A flow starts when the user begins a job. It ends when they get a clear outcome. "Open billing page" is not a useful boundary. "Update payment method and see it saved" is.

That small shift makes feature folders easier to name and easier to defend when the product changes.

Own the outcome, not every piece

When two flows share a step, put that step in the feature that owns the result. Teams move shared-looking code out too early all the time. The result is a common folder full of half-related parts that nobody wants to touch.

Say users can upload a document during onboarding and also inside account settings. The upload widget may look the same, but the jobs are different. Onboarding owns "finish setup." Settings owns "replace an existing document." If the rules, copy, and success states differ, keep each flow inside its own feature.

Only move something to a shared area when it is truly generic. Good examples are a button, a date formatter, a file size helper, or a simple API client. Those pieces do not belong to one user outcome.

A quick test helps:

  • If you change this code, do you expect one user flow to change or several?
  • Does the code carry business rules, or is it just a basic building block?
  • Would a new team member know where to edit it without opening half the project?

If the answer points to one outcome, keep it inside that feature. If it supports many flows without carrying product rules, move it to shared. The line will not be perfect on day one, but it stays much cleaner than a global folder full of "reusable" code that is not really reusable.

Build one feature folder step by step

If your app already feels tangled, do not start with a rewrite. Pick one flow that annoys the team every week. "Invite teammate" or "reset password" usually works well because each one has UI, form rules, API calls, and helper files scattered around the app.

Create one folder for that flow and move only the files that belong to it. Keep the moves small. After each move, fix the imports, run tests, and open the screen in the browser. It takes longer than a giant drag and drop, but it saves you from a painful afternoon.

A simple feature setup often starts with components for that flow, hooks or local state used only there, API files for that backend work, and the validation, types, and small helpers that do not belong elsewhere.

Then add one public entry file, often an index.ts. That file should export only the parts other areas of the app can use. Maybe that is a page component, one hook, and a type. Everything else stays private inside the folder. That boundary matters. It stops other features from reaching in and depending on random internal files.

Keep renaming as you go. Old names like sharedFormUtils or commonModal often hide code that is not really shared. If a helper only exists for the invite flow, give it a direct name and keep it there. Clear names usually do more for frontend code organization than another layer of folders.

Stop earlier than you think. You do not need the perfect structure on day one. Stop when a new developer can open one folder and understand how the flow works without searching the whole codebase.

That is the test: one place, one job, and no surprise dependencies leaking out. Once you get that, the flow is ready for product changes without another global cleanup.

Decide what stays shared

Plan a Safer Refactor
Oleg can help your team reorganize one flow without turning it into a rewrite.

A shared folder should stay small. Once it turns into a storage box for random code, every change gets slower because nobody knows what is safe to edit.

Move code into shared only when several features use it in the same way. If one feature uses a button, input, or modal with special rules, keep that version inside the feature folder. A checkout form may use an input that looks generic but also checks shipping limits or tax fields. That is checkout code, not shared UI.

The same rule applies to helpers. Shared helpers should do plain work, like formatting a date or normalizing a phone number. They should not decide who gets a discount, when a user can cancel, or which warning message appears. Those rules belong next to the feature that owns them.

API code often sits in the middle. If several features call the same backend in a similar way, a shared client makes sense. Auth, user session, or account profile requests often fit there because many parts of the app need them. But if only the returns feature calls a refund endpoint, keep that client in the returns folder until another feature really needs it.

A quick test for shared code

Before you move a file, ask a few simple questions:

  • Do at least two features use it without feature-specific changes?
  • Does its name still make sense outside the original folder?
  • Does it avoid product rules or screen-specific behavior?
  • If you deleted one feature, would this file still matter?

This is where feature folders either stay healthy or fall apart. Teams often create a shared folder too early because it feels tidy. It usually does the opposite.

A good shared folder has boring code. That is a compliment. The more product meaning a file carries, the closer it should stay to the feature that owns it.

A simple checkout example

Checkout is a good test because product teams change it constantly. Shipping rules change, payment options change, and someone always wants to add one more step. If your folders match the user's job, those changes stay local instead of spreading across the app.

One clean approach is to let checkout own everything a buyer needs to finish a purchase: cart review, address, payment, and order summary. That keeps the flow easy to read for any developer who opens the code later.

features/
  checkout/
    cart-review/
    address/
    payment/
    order-summary/
    promo-code/
    totals/
    api/
    state/
    ui/

This works because the files follow the purchase flow, not technical categories. A developer fixing a tax bug or adding Apple Pay can stay inside checkout/ most of the time.

Promo codes are a common source of messy frontend code. Teams often move them into a shared discounts folder too early. It sounds neat, but it usually makes things harder. If a promo code only changes what happens during purchase, keep it in checkout. It belongs to that job.

Saved addresses are different. At first, they can live inside checkout too. A shopper picks an address, edits it, and moves on. Once the same address book shows up in account settings, subscription management, or returns, then it makes sense to move that part into account/ or a shared area. Move code when more than one real feature needs it, not when reuse might happen later.

Order totals should also stay close to checkout rules. A separate math/ or pricing-utils/ folder sounds tidy, but it hides the business logic that matters most. Totals are not just numbers. They include tax, shipping, discounts, rounding, and sometimes region rules. When those files sit near the order summary and payment step, the whole flow is easier to understand.

That is why feature folders usually hold up better during frontend refactoring. The code follows the purchase job, so teams can change checkout without pulling half the app apart.

Mistakes that create churn

Untangle Frontend Ownership
Clarify who owns rules, API calls, and UI before the next product update lands.

Most churn does not come from product change itself. It starts when the folder structure makes a small update feel bigger than it is.

A common mistake is splitting the app into components, hooks, and services at the top level. That looks tidy for a week, then the old problem comes back with new names. A team changing one user job, like how a customer pauses a subscription, has to search several folders just to understand one flow.

Ownership gets fuzzy fast. The UI lives in one place, fetch logic in another, form rules somewhere else, and no folder tells you who should make the change.

The opposite mistake causes trouble too. Some teams make feature folders so small that each modal, table, and hook gets its own mini feature. It sounds modular, but it often means more jumping, more imports, and more time spent tracing dependencies.

If a simple change sends a developer through six tiny folders, the structure is working against the product. A feature folder should hold a meaningful slice of user work, not every small piece of code with a name.

Another source of churn is hiding business rules in shared utilities. Shared code should stay boring. Date formatters, generic input helpers, and simple API wrappers belong there. Rules like "who can refund," "when checkout can skip shipping," or "which plan shows this screen" should stay close to the feature that owns them.

Once business logic leaks into shared, it spreads. Other features start calling it, small changes feel risky, and nobody knows who can safely edit it.

Renaming folders every sprint is the last trap. Teams rename billing to plans, then to subscriptions, then to revenue, hoping the next label will fix the confusion. It rarely does. If the boundary is wrong, a prettier name will not save it.

A few warning signs show up early:

  • One product change touches folders across the app.
  • Teams ask "who owns this rule?" more than once.
  • Shared utilities contain product decisions.
  • Small refactors create large rename-only pull requests.

Good feature folders age well. You should change code inside them often and change their names rarely.

Quick review checklist

Bring Order to Refactors
Get outside help to cut repo friction without freezing product work.

A folder structure passes the test when a developer can make a normal product change without opening half the project. If a small request turns into a scavenger hunt, the structure is already slowing the team down.

Use this check during reviews or before a refactor. Keep it simple. If the answers feel fuzzy, the code probably is too.

  • Can one person trace a user task from screen to state, validation, and API calls inside one folder?
  • Do folder names sound like the roadmap and product brief?
  • Does shared code stay small and boring?
  • Could you delete one feature folder without breaking unrelated areas?
  • Would a new hire guess where the next change belongs within a few minutes?

This review works best on real tickets. Pick one recent change, like "add gift message to checkout," and replay it against the tree. In a healthy setup, the path feels obvious. In a messy codebase, people bounce between shared hooks, global state files, and random components.

That feeling matters. When the path is obvious, teams ship faster, review faster, and break less during frontend refactoring.

What to do next

Pick one feature that changes almost every sprint. Choose something with real pressure, like checkout, onboarding, or account settings. That gives you a fair test. If the folder structure helps there, it will usually help in calmer parts of the product too.

Do not start by moving half the app. Take one feature, put the UI, state, actions, tests, and small helper files close together, and leave the rest alone for now. A narrow test beats a big cleanup project that stalls after a week.

Write one short rule for the folder before anyone adds new files. Keep it plain: "If code only helps checkout work, it stays in checkout. If two features use it and both teams agree on the same shape, move it to shared." A rule like that prevents the usual arguments later.

A small checklist helps:

  • Start with one feature that changed in the last two sprints.
  • Move only the code that feature needs every day.
  • Keep the shared-code rule to one or two sentences.
  • Leave old folders in place until the new structure proves itself.

Then let the team use it for two releases and watch normal work. Did people find files faster? Did small changes stay inside the feature folder? Did shared start growing for no good reason? Those answers matter more than whether the structure looks neat on day one.

After two releases, adjust the boundaries. If one folder keeps collecting unrelated code, split it. If two folders duplicate the same logic, pull that part out. Good frontend code organization usually improves through a few small corrections, not one perfect plan.

If the team feels stuck, it helps to get an outside review before rewriting everything. Oleg Sotnikov at oleg.is works as a fractional CTO and startup advisor, helping companies clean up architecture, infrastructure, and AI-driven development workflows without turning the work into a long rebuild.

Start with one folder this week, and judge it by how easily your team ships the next change.

Frequently Asked Questions

When should I switch to feature folders?

Switch when one product change makes you edit files in components, hooks, api, and tests across the repo. If developers spend more time searching than changing one user flow, your current setup is slowing the team down.

What counts as a feature?

Treat a feature as one user job with a clear result, like checkout, billing, or reset-password. “Open billing page” is too thin; “update payment method and see it saved” is a much better boundary.

How should I name feature folders?

Use product words your team already uses, such as checkout, invite-team, or account-settings. Skip technical names like payment-module unless that name matches a real product area.

What should stay inside a feature folder?

Keep the UI, local state, validation, API calls, tests, and small helpers for that job together. If code only helps one flow work, leave it inside that feature.

What belongs in a shared folder?

Move only plain building blocks into shared, like buttons, date formatting, file size helpers, or a basic API client. Once a file carries discount rules, tax checks, or screen copy, keep it near the feature that owns that rule.

Should I reorganize the whole app at once?

No. Start with one painful flow, move a few files, fix imports, run tests, and ship that change. A small move tells you whether the structure helps before you touch the rest of the app.

Why add an index.ts file to a feature folder?

A single index.ts gives other parts of the app one clear entry point. That stops random imports from internal files and makes later cleanup much easier.

What if two features use similar code?

Keep separate versions until the behavior truly matches. An upload box in onboarding and one in settings may look the same, but different rules, copy, and success states mean they belong to different features.

How can I tell the current structure is causing churn?

Watch normal tickets. If a small change sends people all over the repo, nobody knows who owns a rule, or shared keeps filling with product logic, the structure is working against you.

How do I know the new structure is working?

Test it with a real ticket, like adding a field to checkout. If one developer can trace the screen, state, validation, and API work in one place without a repo-wide search, the setup works.