A good frontend project structure does not make your app better by itself, but it makes everything around the app easier: onboarding, debugging, testing, refactoring, and shipping changes without breaking unrelated features. This guide gives you a practical way to organize React, Vue, and vanilla JavaScript projects so they stay understandable as they grow. Instead of treating folder structure as a style debate, we will look at how to group code by responsibility, when to group by feature, where shared utilities belong, and how to avoid the common layouts that feel tidy at first but become expensive to maintain later.
Overview
If you are starting a new frontend app or cleaning up an existing one, the main goal is simple: make it obvious where code should live and why. A scalable frontend project structure should help a developer answer a few questions quickly.
- Where is the UI for this feature?
- Where is the business logic?
- Where are the API calls?
- What code is reusable across the app?
- What is safe to edit without causing unrelated side effects?
That sounds basic, but many codebases become hard to work in because they organize files around convenience in the first week rather than clarity in month six. A small project can survive almost any structure. A growing one cannot.
Across React, Vue, and vanilla apps, the same broad principles usually hold:
- Keep related code close together. A feature should not be scattered across ten top-level folders if it can be understood as one unit.
- Separate domain logic from presentation. UI files should not become a dumping ground for data shaping, API orchestration, and validation rules.
- Make shared code intentionally shared. If everything goes into
utils, then nothing is really organized. - Prefer consistency over cleverness. New contributors should not need a map to understand naming and placement.
- Let size determine sophistication. A landing page, a dashboard, and a design system should not all use the same depth of architecture.
One useful way to think about frontend project structure is that folders are not just storage. They are documentation. They tell your team how the app is shaped.
Core framework
Here is a practical framework you can use for frontend project structure regardless of stack. The exact folder names may vary, but the intent should stay stable.
1. Start with app-level boundaries
At the highest level, most frontend apps benefit from a few predictable areas:
apporsrcfor application codeassetsfor static resourcescomponentsfor truly shared UI piecesfeaturesfor feature-specific modulesservicesorapifor external communicationlibfor framework setup and integrationshooks,composables, orstatefor reusable behaviorstylesfor global styling concernstestsor colocated test files
If your app is small, you may not need all of these. The point is not to force folders into existence. The point is to make boundaries explicit once the app starts to grow.
2. Prefer feature-first organization for medium and large apps
One of the biggest frontend best practices is moving away from a purely type-based structure. Type-based means top-level folders like this:
src/
components/
pages/
hooks/
services/
utils/
This can work for small apps, but it often leads to fragmentation. A single feature might require a page, several components, a state module, a validator, tests, and API code. If those are all separated by file type, understanding one feature means jumping around the entire repository.
A feature-first structure usually scales better:
src/
features/
auth/
components/
api/
hooks/
validation/
types/
tests/
billing/
components/
api/
state/
tests/
dashboard/
components/
charts/
services/
tests/
components/
lib/
styles/
This does not eliminate shared code. It just makes feature ownership clearer. If code belongs only to billing, keep it in billing. If it becomes broadly reusable, promote it to a shared area later.
3. Create rules for shared code
Many frontend projects become messy because shared folders have no definition. In practice, you should distinguish between these categories:
- Shared UI components: buttons, modals, inputs, cards, layout wrappers
- Shared domain helpers: date formatting for invoices, currency display, permission checks
- Shared infrastructure: HTTP client, logger, analytics setup, error reporting
- Generic utilities: string helpers, array transforms, serialization helpers
Do not send all of them into one catch-all directory. A huge utils folder is often a sign that code is shared too early or named too vaguely.
If your app handles data from APIs, it is worth having a clean place for request logic and response transformation. For related backend concerns, a team often benefits from documenting API assumptions separately; a resource like the REST API Testing Checklist is useful when frontend and backend work meet.
4. Keep routing and entry points shallow
Whether you use React Router, Vue Router, or a lightweight custom setup, routing should be easy to scan. Route declarations are one of the first places developers look when they need to understand how the app is composed.
Try to keep route files focused on route mapping, lazy loading, and guards. Avoid hiding heavy business logic inside router definitions. Each route should usually point to a page-level module that becomes the entry point for the feature.
5. Colocate tests, styles, and docs where it helps
Colocation means keeping supporting files near the code they describe. This often works well for:
- component tests
- feature tests
- story files
- local styles or CSS modules
- README notes for feature-specific setup
Colocation reduces context switching. If a component has a dedicated test, a style file, and a story, there is real value in seeing those pieces together.
For broader project documentation, keep a clear root-level README and use predictable markdown structure. If your team publishes internal setup notes often, a guide like the Markdown Previewer Guide can help keep those docs readable.
6. Separate configuration from application behavior
Frontend apps usually accumulate config for bundlers, linters, test runners, formatting tools, environment handling, and deployment. Keep this distinct from application logic. Config files should be named consistently and documented if they are not obvious.
If your project relies on multiple config formats, it helps to be deliberate about when to use each. The article JSON vs YAML vs TOML is a useful companion when you are deciding how to structure project configuration and developer tooling.
7. Name folders by domain, not by implementation detail
Folder names like misc, helpers, commonStuff, or temp usually create ambiguity. Better names describe the domain or responsibility:
authinstead ofloginStuffpaymentsinstead ofbillingHelperspermissionsinstead ofsharedLogichttporapi-clientinstead ofnetworkUtils
Good naming makes architecture visible without opening every file.
Practical examples
The best project structure depends on app size and team needs. Here are three working patterns you can adapt.
Example 1: Small React app
If you are building a small React app with a handful of pages, do not overengineer it. A straightforward react folder structure might look like this:
src/
app/
router.tsx
providers.tsx
pages/
HomePage.tsx
SettingsPage.tsx
components/
Button/
Button.tsx
Button.test.tsx
Modal/
Modal.tsx
features/
auth/
LoginForm.tsx
useAuth.ts
authApi.ts
lib/
http.ts
styles/
globals.css
main.tsx
This works because there are only a few pages and one real feature module. Shared UI goes in components, app setup goes in app, and domain-specific code lives under features.
Use this when:
- the team is small
- the domain is still evolving
- you want a structure that can grow without needing a rewrite next month
Example 2: Medium Vue application
A growing Vue app usually benefits from clearer module boundaries. A practical vue project organization can look like this:
src/
app/
router/
store/
features/
orders/
components/
composables/
api/
views/
types/
customers/
components/
composables/
api/
views/
reports/
components/
charts/
api/
components/
ui/
BaseButton.vue
BaseInput.vue
lib/
axios.ts
assets/
styles/
In this setup, each feature contains the views and logic needed for that domain. The shared ui layer stays small and focused. This reduces the temptation to create a global component folder full of one-off files that only one feature uses.
Example 3: Vanilla JavaScript app with clear modules
Vanilla apps deserve structure too. If you are not using a framework, you still need a coherent javascript app architecture.
src/
app/
init.js
router.js
features/
search/
search-view.js
search-service.js
search-state.js
profile/
profile-view.js
profile-service.js
shared/
dom/
render.js
events.js
http/
client.js
utils/
format-date.js
styles/
main.css
The key is the same: group by feature where possible, and keep shared infrastructure explicit.
How to decide what becomes a feature
A useful rule is this: if a set of files changes together, test together, and serves one business area, it is probably a feature. Examples include:
- authentication
- checkout
- notifications
- user settings
- reporting
By contrast, generic buttons, date pickers, and global theme tokens are usually shared building blocks rather than standalone features.
How to handle data and API code
Frontend teams often disagree on whether API requests should live globally or inside each feature. In most cases, feature-owned API code is easier to maintain. For example, order fetching belongs in the orders feature unless it is truly part of a shared data layer.
When working with JSON-heavy APIs, keep transformation and validation close to the feature that consumes them. If you often debug malformed payloads, a companion resource like the JSON Formatter and Validator Guide is worth bookmarking.
Likewise, if authentication flows involve token debugging, your app documentation can point teammates to a safe inspection process such as the JWT Decoder Guide.
Common mistakes
Most folder structures fail for predictable reasons. If you avoid these, your project will already be easier to maintain than many codebases.
Everything is shared too early
Developers often move code into global folders the moment it looks reusable. That usually creates accidental coupling. Keep code local to a feature until there is a strong reason to promote it.
The utils folder becomes a junk drawer
A folder named utils tends to grow without rules. Soon it contains HTTP wrappers, business rules, date formatting, query parsing, feature toggles, and abandoned helpers. Split utilities by concern or keep them close to the feature they serve.
Pages become too smart
Page files often accumulate data fetching, state wiring, transformation logic, and rendering. Over time they become hard to test and easy to break. Keep pages as orchestration layers, then move reusable behavior into hooks, composables, services, or domain modules.
Shared components are not actually reusable
If a component lives in a shared folder but contains order-specific labels, report-specific assumptions, or checkout-only styling rules, it is not shared. Move it back into the feature.
Folder names reflect the framework, not the business
Structures built around technical patterns alone can make the app harder to understand. Your teammates usually think in terms like users, subscriptions, invoices, and settings, not in terms like containers, handlers, and wrappers.
No documented structure rules
Even a good project layout can drift if nobody writes down the conventions. Add a short architecture note to the repository explaining:
- what belongs in a feature folder
- when code may move to shared folders
- where API clients live
- how tests are organized
- how naming should work
This does not need to be long. One page of guidance can prevent months of entropy.
Refactoring structure without refactoring imports and dependencies
Moving files around without improving dependency direction often changes appearance more than architecture. A clean tree is not enough if features still import each other unpredictably. Aim for clear dependency flow: app shell depends on features, features depend on shared infrastructure, and shared infrastructure does not depend on feature code.
When to revisit
You do not need to redesign your frontend project structure every sprint. But you should revisit it when the shape of the app changes. This is the practical checkpoint section to return to whenever a codebase starts feeling harder to navigate.
Review your structure when:
- a small app becomes a multi-feature product
- new developers struggle to find the right place for code
- shared folders grow faster than feature folders
- pages or views become very large
- test setup feels inconsistent
- framework conventions change in a meaningful way
- build tooling or deployment patterns introduce new boundaries
A simple maintenance checklist helps:
- Audit top-level folders. Can each one be explained in one sentence?
- Inspect your largest feature. Is related code colocated, or scattered?
- Review the shared layer. Which files are genuinely cross-cutting, and which should move back into features?
- Check dependency direction. Are features importing each other in ways that create hidden coupling?
- Update architecture notes. Make the current rules visible to the next contributor.
If you are starting from a messy codebase, do not attempt a total rewrite of the folder tree in one pass. A safer approach is incremental refactoring:
- pick one feature
- group its UI, state, and API logic together
- move only clearly shared code into shared folders
- add tests around risky behavior
- repeat feature by feature
The best frontend project structure is not the most elaborate one. It is the one that makes change safer and reasoning easier for your team. If your React, Vue, or vanilla app lets developers quickly locate code, understand ownership, and add features without spreading logic across the repository, your structure is doing its job.
As your tooling matures, it also helps to support the structure with good developer utilities. For a broader toolkit, see Best Free Developer Tools Online. And if AI is part of your workflow for refactoring, scaffolding, or repository analysis, Picking the Right LLM for Developer Workflows offers a useful next step.
Before you close this guide, do one concrete action: open your current frontend repository and trace one feature from route to UI to state to API call. If that path is difficult to follow, your next refactor target is already clear.