How to organize and split your frontend applications - showcase of 8 different setups with complete code examples
This is the default setup I would recommend for most frontend teams. I have covered some of the advantages here, the biggest ones being simplification, consistency and easier cross-team contributions through a centralized and opinionated setup.
When your applications are live under different domains, for example https://website-home.com and https://website-blog.com, you can still use a monorepository to serve your different frontend applications.
You have distinct apps serving different purposes and you want different applications and/or teams to align and share their development setup and practices to encourage reusability and reduce overall efforts on developer experience and dependencies management.
Pros:
- Streamlined development with shared opininated configurations (Typescript, formatting, linting etc.) that allow developers to focus on delivering business value.
- You no longer have to synchronize shared dependencies releases and updates accross many scattered repositories.
- Cross team contributions are much easier.
- One (possibly virtual) team can focus on operational work (dependency management, security maitenance, local developer experience, CI/CD, devops, etc.), and all teams will benefit from it.
Cons:
- You have to invest in the initial setup for example to setup monorepository tooling.
- You have to invest into a collaboration model and a proper code architecture for the monorepository (which is a benefit in disguise).
With this setup, it is simple and easy to share local package dependencies between apps such as UI modules or a horizontal enablement integration like analytics as they are linked locally.
Sharing runtime dependencies - See Why runtime dependencies?
This is where Module Federation come in handy. The second version of Module Federation is supported by all major bundlers and allows you to share code and resources among multiple JavaScript application at runtime without the need to package them.
When your applications are live under the same domain but at different URLs, for example https://website.com/home and https://website.com/blog, then you will have to use a reverse proxy at some point in your stack to be able to route the traffic to your different frontend applications. You can still use a monorepository to serve your different frontend applications.
You have distinct apps serving different purposes and you want different applications and/or teams to align and share their development setup and practices to encourage reusability and reduce overall efforts on developer experience and dependencies management.
Pros:
- Streamlined development with shared opininated configurations (Typescript, formatting, linting etc.) that allow developers to focus on delivering business value.
- You no longer have to synchronize shared dependencies releases and updates accross many scattered repositories.
- Cross team contributions are much easier.
- One (possibly virtual) team can focus on operational work (dependency management, security maitenance, local developer experience, CI/CD, devops, etc.), and all teams will benefit from it.
Cons:
- You have to invest in the initial setup for example to setup monorepository tooling.
- You have to invest into a collaboration model and a proper code architecture for the monorepository (which is a benefit in disguise).
With this setup, it is simple and easy to share local package dependencies between apps such as UI modules or a horizontal enablement integration like analytics as they are linked locally.
Sharing runtime dependencies - See Why runtime dependencies?
This is where Module Federation come in handy. The second version of Module Federation is supported by all major bundlers and allows you to share code and resources among multiple JavaScript application at runtime.
There are use cases where seperate repositories per applications make sense. The most common ones are:
- Compliance reasons: payment applications to adhere to Payment Card Industry Data Security Standard (PCI DSS) where you need stronger governance and audit trails.
- High independence between teams: though there are negative consequences to this, you might want to prefer the trade offs of highly siloed, independent teams.
When your applications are live under different domains, for example https://website-home.com and https://website-blog.com, then it is straightforward to use different repositories to serve your different frontend applications.
You have distinct apps serving different purposes which are managed by different large teams with good frontend expertise in each and you do not want them to share the same infrastructure.
Pros:
- Teams will be able to operate in competely autonomy so they are able to choose different tech stacks and release at different paces.
- Hosting and deployment of those different applications is simple as one repository maps to one domain.
Cons:
- Teams will each have to dedicate time to develop and maintain duplicated infrastructure in terms of dependency management, security maitenance, local developer experience, CI/CD, devops, etc.
- Cross team contributions will be more difficult if the tech stacks and code architecture start to diverge.
- While possible, sharing dependencies between those teams (design system, common librairies) will not scale well with the number of dependencies and frontend applications.
With this setup, it is time consuming to share package dependencies between apps such as UI modules or a horizontal enablement integration like analytics as you need to publish and update them in each repository.
Sharing runtime dependencies - See Why runtime dependencies?
This is where Module Federation come in handy. The second version of Module Federation is supported by all major bundlers and allows you to share code and resources among multiple JavaScript application at runtime without the need to package and update them in all your repositories. This is even more powerful in a multiple repositories setup as it completely remove the need to update these dependencies manually in all your repositories.
When your applications are live under the same domain but different URLs, for example https://website.com/home and https://website.com/blog, then you will have to use a reverse proxy at some point in your stack to be able to serve the different frontend applications mapped to different repositories.
You have distinct apps serving different purposes which are managed by different large teams with good frontend expertise in each and you do not want them to share the same infrastructure.
Pros:
- Teams will be able to operate in competely autonomy so they are able to choose different tech stacks and release at different paces.
- Hosting and deployment of those different applications is simple as one repository maps to an URL subpath mapped to your reverse proxy.
Cons:
- Teams will each have to dedicate time to develop and maintain duplicated infrastructure in terms of dependency management, security maitenance, local developer experience, CI/CD, devops, etc.
- Cross team contributions will be more difficult if the tech stacks and code architecture start to diverge.
- While possible, sharing dependencies between those teams (design system, common librairies) will not scale well with the number of dependencies and frontend applications.
With this setup, it is time consuming to share package dependencies between apps such as UI modules or a horizontal enablement integration like analytics as you need to publish and update them in each repository.
Sharing runtime dependencies - See Why runtime dependencies?
This is where Module Federation come in handy. The second version of Module Federation is supported by all major bundlers and allows you to share code and resources among multiple JavaScript application at runtime without the need to package and update them in all your repositories. This is even more powerful in a multiple repositories setup as it completely remove the need to update these dependencies manually in all your repositories.
Runtime dependencies are much easier to synchronize accross apps as you can simply redeploy them for every apps to have their latest version, eliminating lots of tedious work. Of course that puts increase risks on those app deliveries so a good quality assurance process is necessary.
The biggest advantage is that you can release hotfixes and new features for these dependencies without having to synchronize and redeploy all your applications.
Common use case include sharing global UI componments like a header or footer as well as horizontal enablement such as analytics, A/B testing or observability.
There are 2 main advantages of using module federation:
- You can declare shared packages dependencies between your host and remotes such as framework dependencies (React, Vue etc) to reduce the overall bundle size while allowing the flexibility of framework development.
- Great developer experience and debuggigng tools. This is where v2 of module federation really shines in my opinion with debugging tools integration such as Chrome DevTools