Structuring Large-Scale React & TypeScript Applications for Enterprise

Structuring Large-Scale React & TypeScript Applications for Enterprise
We want our code to be easy to work with and scale well at Arisecraft Tech..
React can be a bit tricky to work with because it does not tell you how to organize your files or manage your data. This can be good when you are just starting out. It can quickly become a mess when you are building big applications, like the ones we make at Arisecraft Tech.
At Arisecraft Tech we have found a way to keep our React and Typescript code organized and easy to maintain. Here is how we do it to keep our code scalable and easy to work with even as our team and products grow..
1. Feature-Driven Folder Structure
When you are building a React app, a lot of people make the mistake. They put all the files of the type together. For example they put all the components in one folder called components and all the hooks in another folder called hooks.
This is a problem when you are working on a big app. Let us say you want to understand how the Invoice feature works. You will have to look in a lot of folders.
We do things differently. We like to group our files by what they're used for. This is called a feature-driven architecture or colocation. So we put all the files that are used for the Invoice feature in one place. This makes it a lot easier to understand how the Invoice feature works. We do the thing for every feature, in our app.
1src/ 2├── components/ # Global, highly reusable UI components (Buttons, Modals) 3├── features/ # Feature-specific modules 4│ ├── invoices/ 5│ │ ├── api/ # API calls related to invoices 6│ │ ├── components/ # Invoice-specific components 7│ │ ├── hooks/ # Custom hooks for invoice logic 8│ │ ├── types/ # TypeScript interfaces for invoices 9│ │ └── index.ts # Public API for this feature 10│ └── customers/ 11├── pages/ # Page-level components that tie features together 12├── lib/ # Third-party library configurations (Axios, third-party wrappers) 13└── utils/ # General helper functions (date formatting, math)
By making each feature export an index.ts file we think of them as modules.
This helps to create boundaries.
It also stops the features from being closely connected across the app.
We treat each feature as its module.
This separation helps keep the app from getting too messy.
2. Leveraging TypeScript as a Safety Net
TypeScript is a must for applications.. Just changing the name of your files from .js to .ts and using any all the time does not help at all.
We make sure to use typing in all of our B2B applications. When we are working with financial transactions and billing data one small mistake with types can cause the user interface to break or even worse the data to get corrupted. We use TypeScript to avoid these kinds of problems, with TypeScript.
Best Practices We Follow:
- We always have Strict Mode turned on. This is because we set
strictto true in ourtsconfig.jsonfile. There are no exceptions to this rule. - We like to share types between our backend and frontend. Our backend is built with Node.js and our frontend is built with React. So when the Invoice model on the server changes the frontend build will fail away if it is not updated.
- We do not like to use
anyfor our types. Instead we useunknownwhen a type is really dynamic. This means the developer has to check the type before they can use the variable. We do this to make sure the developer is careful with the types. We useunknownfor our types like I said before and this is our rule for using types, with the Invoice model and other things.
3. Rethinking State Management
A few years ago people used Redux for managing state. Now we split state into three types. Handle each one differently:
-
Server State: This is data that lives on the server. For example a list of users or quotation data. We use libraries like React Query or SWR for this. They automatically handle caching refetching data in the background and states like loading or error. This takes care of most of what we used to do with Redux.
-
Global UI State: This is data that needs to be accessed from anywhere in the app but isn't stored in the database. Examples include a mode toggle, an open navigation drawer or authentication state. React. Small libraries like Zustand work well for this.
-
Local Component State: This is state like the input of a controlled form or a dropdown toggle. This type of state should be managed with
useStateoruseReducerinside the component. When we keep Server State separate, from Global UI State our application runs fast and is much easier to debug.
4. Separation of Logic and Presentation
Large React components can easily bloat to 500+ lines if you mix API calls, complex business logic, and JSX markup. We mitigate this by heavily utilizing Custom Hooks.
If a component needs to fetch data, calculate totals, and manage multiple sub-states, we abstract that logic into a hook.
Instead of this:
1const InvoicePage = () => { 2 const [data, setData] = useState(); 3 // ... 50 lines of fetching logic, formatting, and calculations 4 return <div>{/* UI */}</div>; 5};
We do this:
1const InvoicePage = () => { 2 const { invoiceData, calculateTaxes, isLoading } = useInvoiceDetails(invoiceId); 3 4 if (isLoading) return <Spinner />; 5 6 return <InvoiceView data={invoiceData} onTaxCalculate={calculateTaxes} />; 7};
This keeps the component purely focused on presentation (the "Dumb Component") while the hook handles the heavy lifting (the "Smart Logic").
5. Standardizing the UI with a Design System
When building B2B software, consistency is key. We never write inline styles or scatter custom CSS classes across feature files. Instead, we invest time upfront in building a robust set of base components in our global components/ directory (e.g., <Button>, <Modal>, <DataTable>).
Everything else in the app is built by composing these base components. Whether we are using TailwindCSS, styled-components, or Vanilla CSS modules, having a single source of truth for design tokens (colors, spacing, typography) ensures the app feels premium and cohesive.
Conclusion
Structuring a large-scale React and TypeScript application requires discipline. By organizing by feature, enforcing strict types, properly categorizing state, and separating logic from presentation, you can build a codebase that is not only scalable but genuinely enjoyable to work in.
At Arisecraft Tech, these principles allow us to build complex, high-performance systems with confidence. If you're starting a new enterprise project, try adopting these patterns—your future self will thank you.
