Back to Blog

How to Structure Angular Applications for Scalability

Avatar
@sercan
November 7, 2025

In this post, I’ll talk about how I structure my Angular projects. It’s not perfect, and I’m sure you’ll question some of the things I do, but that’s fine.

As with many others, we Angular developers also struggle with structuring applications when they reach a certain point. Whether it’s a brand new project or something we took over, sooner or later it reaches a stage where we can’t look at it anymore without feeling the chaos we or others have created.

In this post, I’ll talk about how I structure my Angular projects. It’s not perfect, and I’m sure you’ll question some of the things I do, but that’s fine. The goal of this post isn’t to give you the perfect solution, because that simply doesn’t exist.

The goal is to hopefully give you ideas around structure and maybe that “Aha!” moment that helps you reshape your own application in the best way possible.

Advice for new projects

Making your project structure scalable shouldn’t be your day one goal if you’re just starting. Without enough context or content, you’ll fall into analysis paralysis, and it’s one of the worst things that can happen early on. It locks you into trying to solve problems you don’t even have yet. The possibility of those problems isn’t enough to help you solve them until you actually face them.

This happens to be one of my weakest points, caused by one of my strongest points. Because I’ve seen and worked on many different projects, I have a good sense of what can go wrong when a project grows in complexity. That sometimes makes me want to solve problems before they even exist.

But even if your project reaches the exact level of complexity you expected, it can still differ in so many ways from what you’ve experienced before, requiring completely different solutions.

For new projects, if you don’t have enough content, don’t try to solve scalability “issues” based on assumptions. Just because you’re building the same type of app you built before doesn’t mean the same structure will work again.

Always assume there will be differences. Of course, that doesn’t mean you should ignore your experience and start with total chaos, throwing files and folders around randomly. Just start with the bare minimum and build from there. Eventually you’ll have enough context to refactor things properly and decide what makes the most sense for your project.

Usual stuff

I’m not going to talk about things like feature-based organization, isolation by intent, or simplicity over abstraction. You can search for them if you’re curious, but I’m pretty sure you’ve already heard of them. They’re good concepts and worth following, but I’m more of a visual learner, so I’d rather not bore you by explaining them again.

When thinking about project structures, don’t aim for “perfection,” aim for “perfection for your case.” Every project is different. Some rely heavily on services while others depend on state management. Some have a huge number of tests, while others simply don’t need that many because of their size. Sometimes you build an application that acts like three different ones in a single codebase, and sometimes you just create a simple dashboard. All of these situations affect how you should structure your project.

The structure

Here’s an overview of my favorite project structure as of writing this post:

src
  app
    core
      analytics
      auth
      env
      error-handler
      icons
    features
      auth
      dashboard
      projects
      reports
      settings
      teams
      users
    layouts
      app
      empty
    shared
      components
      directives
      pipes
      utils
    app.config.server.ts
    app.config.ts
    app.routes.server.ts
    app.routes.ts
    app.ts

I’m not sure who first came up with the core and shared folders, but I can tell you I was one of the early adopters because of Fuse Angular. They just work, both conceptually and practically. You’ll see people trying to reinvent them now and then, but honestly, they hold up. After working with Angular(.js) for over ten years, the core and shared concepts still make sense to me and never feel outdated.

features are the new modules, basically. I still use the name “module” sometimes depending on the complexity, but only as a concept for grouping things, not as Angular’s old NgModule approach.

layouts is a newer concept I’ve started using and it’s been working great so far.

Let’s look at each of them in detail.


core

I don’t think I need to explain what the core folder is for, but for the sake of completeness, here’s a quick rundown.

The core folder contains services and functionality that are essential to the app’s operation. Think of it as the engine of your car. Without it, you’re not going anywhere. Examples of what belongs in core:

  • Authentication service
  • User service
  • HTTP interceptors
  • Global error handling
  • Logging service
  • Global state store (if you use one)
  • Route guards
  • App-wide configuration services

Here’s an example of the core folder from builderkit.dev:

core
  analytics
  api
  auth
  env
  error-handler
  highlighter
  icons
  scroll-margin
  seo
  title-strategy

shared

The shared folder is your toolbox. It contains utilities and reusable tools that you might use in various places, but the app could still run without them. Examples of what belongs in shared:

  • UI components (buttons, cards, modals, etc.)
  • Pipes
  • Directives
  • Utility functions
  • Interfaces and models used across features
  • Common services that aren’t app-critical (for example, a confirmation service)

Here’s an example from builderkit.dev:

shared
  components
    clipboard-copy.ts
    cta.ts
    highlight.ts
    scheme-switcher.ts
  utils
    bui.ts
    debounce.ts
    signal-data-source.ts
    signal-utils.ts

💡 A simple rule of thumb:

  • If removing it breaks the app, it belongs in core.
  • If it’s used in multiple places but the app still runs fine without it, it belongs in shared.

features

This is where all of your application features live. I strongly recommend having a strict folder structure for each feature, since this is where you’ll spend most of your time. I personally use fuzzy file search a lot, but even then, having consistent structure helps. For example, I can search for:

“auth/routes” to open the routes.ts file of the Auth feature, or “auth/pages” to jump into the pages folder of the same feature.

Here’s a realistic example:

features
  customers
    components
      customers-form
        tabs
          account.ts
          general.ts
          notes.ts
          profile.ts
          security.ts
        customers-form.ts
      customers-table
        customers-table.ts
        table-customizer.ts
        table-filters.ts
      no-customers
        no-customers.ts
    models
      customer.ts
      customer-api.ts
    pages
      customers-details.ts
      customers-list.ts
      customers-new.ts
    services
      customers-service.ts
    layout.ts
    routes.ts

It may look complex, but it’s realistic. Once we’re inside a feature folder, I stop applying “feature-based organization.” There’s no need to subdivide further, since many concepts within a feature will overlap. For example, the NoCustomers component can appear in multiple situations: when there are literally no customers or when filtering yields no results. Trying to further split the feature just creates confusion.

Instead, I use a classic “organize by concept” approach: reusable components go under /components, types under /models, and so on.

The /pages folder contains routed components. These components are listed in the routes.ts file and serve as orchestrators for what’s displayed on a given route. For example, customers-list.ts might contain a CustomersTable component and a TableFilters component connected together through inputs and outputs.

The routes.ts file defines routing for the feature. Separating feature routing improves modularity and lazy loading. If a user doesn’t have access to a feature, they won’t load any of its code.

The layout.ts file defines the specific layout for the feature, such as a sidebar or secondary navigation unique to that area.


layouts

This one is fairly new for me, but I love it. I used to put anything layout-related inside shared, but over time I realized layouts aren’t really shared. They deserve their own space because they can have multiple parts: side navigation, header, footer, user menu, and more. These pieces belong together, not in shared.

You might argue that layouts are features and should go under features, but I see it differently. Yes, they’re part of the app, but they’re not user-facing, so I prefer to keep them separate.

Could they go in core then? Possibly, but core in my mind is about logic, not visuals. It’s about features that have no UI, like authentication or configuration. Putting layouts there would break that pattern, so I gave them their own home.

layouts
  app
    components
      navigation.ts
      sidebar.ts
    layout.ts (AppLayout)
  empty
    layout.ts (EmptyLayout)

Structure flexibility

One thing I love about this structure is how flexible it is. I’ve been able to adapt it to very different types of projects.

For example, in one project, I had to work in a monolithic structure where three main roles needed entirely different features and layouts: Teacher, Student, and Admin.

app
  src
    core
    modules
      admin
      auth
      student
        features
        layouts
        shared
        routes.ts
      teacher
    shared
      components
        button
      utils
        date-utils.ts
    app.config.server.ts
    app.config.ts
    app.routes.server.ts
    app.routes.ts
    app.ts

Here I replaced features with modules to separate major areas of the application. All roles shared the same authentication screen, so auth became a top-level module.

Each module repeated the same structure with its own features, layouts, and shared folders. This allowed me to group things cleanly while keeping everything inside a single codebase.


In another project, which was a marketing website, I used almost the same pattern again with slight adjustments:

src
  app
    core
    features
      blog
      contact-us
      home
      legal
      team
    layouts
      web
        components
          footer.ts
          header.ts
          mobile-menu.ts
        layout.ts (WebLayout)
    shared
      components
      utils
    app.config.server.ts
    app.config.ts
    app.routes.server.ts
    app.routes.ts
    app.ts

Each feature represented a different page. Only blog and legal had their own routes.ts or layout.ts, while others were single-page features. Again, the same structure adapted perfectly.

State management and tests

I don’t use separate state management libraries in Angular. With the introduction of Signals, services are powerful enough.

But if you’re using one, or working in a project that does, you can easily fit it in by adding a state folder under each feature. For small features, a single state.ts file works fine.

features
  customers
    components
    directives
    pages
    state (or state.ts)
    layout.ts
    routes.ts

For tests, I don’t recommend a separate test folder inside features. Tests should live as close as possible to what they’re testing. So if you’re testing a component, place the spec file next to it:

features
  customers
    components
      customers-table
        customers-table.ts
        table-customizer.ts
        table-customizer.spec.ts
        table-filters.ts
        table-filters.spec.ts

If you need global mocks or test utilities, you can add a test folder at the project root:

src
  app
    core
    features
    layouts
    shared
    test
      local-storage.mock.ts
      customers-data.mock.ts

Closing

As I mentioned at the beginning, this isn’t about giving you a perfect structure, but about helping you find that “Aha!” moment.

I use this exact structure in builderkit.devand all of its products. Spark, the starter template, follows it. The app you’re viewing right now uses the same structure too.

With the release of the first batch of blocks, I’m now focusing on expanding the templates while continuing to build new blocks.

Thank you for reading.