This article is part of the series discussing how we developed a new global application, and covers some of the decisions made for the iOS application. If you haven’t already, I would suggest checking our deeeeeet’s article here for an overview of the project.
Introduction
Over the years, Mercari has built each new iOS application in an independent repository using different tech stacks—fully native, React Native, and Flutter. For the Global App, we took a different approach: we migrated the existing Mercari App repository into a monorepo structure that could host multiple products, and began developing within it using the same technology. This decision was based on the strategic conclusion that we could maximize the utilization of the foundation, massive knowledge base, and proven platform components.
This article explains how we’ve restructured an existing repository into a monorepo, and shares the decisions behind the migration, along with the lessons learned.
Throughout this article, we use the following terminology:
- Mercari App: Our existing Mercari app in Japan
- Global App: The newly developed Mercari Global App
Note: This article focuses only on iOS, but we’re taking a similar approach for Android.
Background
As mentioned earlier, Mercari has experimented with multiple approaches for iOS applications. Here are some previous examples:
| Product | Technology | Repository | Notes |
|---|---|---|---|
| Mercari JP (original app) | Native iOS | Original repository | – |
| Mercari US (1st version), Mercari UK | Native iOS | Same repository as Mercari JP | Switching behaviors based on compiler directives, without a modular architecture. Later, US and UK each started forking the repository due to the complexity of managing different applications and behavior changes in the same repository. |
| Mercari US (2nd version) | Hybrid (Native iOS + React Native) | New repository | – |
| Mercari Atte, Mercari Kauru | Native iOS | New repositories | – |
| Mercari US (3rd version) | Full React Native | New repository | Our React Native Evolution |
| Mercari JP (new app) | Native iOS | New repository | Recreated the original Mercari JP app from scratch as "GroundUp App". "Just Wait Till You See What’s Next for Mercari Engineering”: The iOS & Android Tech Leads Recap the “GroundUp App” Project |
| Mercari Hallo | Flutter | New repository | Mercari Hallo’s Tech Stack and Why We Chose It |
This trajectory is also described in the following presentation (Japanese only).
Mercari 10years iOS Development
Beyond the differences in technology stacks, there were two major strategic directions to consider: developing as a new, independent repository separate from the existing Mercari App, or developing within the same repository as the Mercari App.
After evaluating the benefits and drawbacks of various approaches we had tried—along with the learnings from them (though I personally experienced only some of these projects)—and considering the long-term objective for the Global App, we decided to develop in the Mercari App’s repository as a monorepo. This led us to reorganize the existing codebase and structure to accommodate the Global App and future applications.
I’ll revisit these benefits and drawbacks later in this article, but let me first explain the steps we took for the migration.
Migration to monorepo
Our repository for Mercari App was fundamentally designed for a single application, not presuming it would handle multiple products. Therefore, we first needed to migrate it to support multiple products—a process which involved reorganizing the existing codebase and structure.
Before diving into the steps we took, I should mention that we use Bazel—this is important background knowledge for this article.
Our Bazel usage
We’ve previously shared our strategy and direction for building the Mercari App with Bazel in the following presentation and blog post:
For detailed explanations, please refer to the resources above. Here, I’ll focus on how this Bazel-based design encouraged us to choose the monorepo approach:
- The micro-modular architecture provides numerous modules with strict boundaries, clear responsibilities, and well-defined abstractions, making them either already reusable or easily refactored for reuse. For reference, between the Mercari App and Global App combined, the total number of modules exceeds 900 today.
- Build efficiency—since we can easily add only the necessary modules as dependencies, we can mitigate risks such as increased build times or bloated binary sizes that typically come with managing a massive codebase in a single repository.
- The existing infrastructure, including remote caching and Remote Build Execution, allows us to start development in an already optimized environment.
- There’s no need to worry about launch performance potentially caused by having hundreds of modules, because Bazel compiles all modules as static libraries by default.
For these reasons, migrating to a monorepo was relatively feasible, and we could benefit from these advantages from day one.
Step 1: Designing the structure to handle multiple products
Before this monorepo migration, our repository looked as follows:
The Applications directory above doesn’t mean it can handle multiple products. It’s designed for different application targets within the same product context—for example, sample applications and app extensions. Libraries contains modules that can be treated as open source, and Group contained other modules used across the application.
After the migration, we aimed for the following structure:
The structure accommodates multiple future products under Products. Each product ships its own application while sharing core modules. This required restructuring modules into separate layers: Products for product-specific code, and Company / InHouse for modules reusable across products. InHouse contains modules that handle company internal services—our global identity platform is one example—and serves a similar purpose to the Company directory.
Step 2: Assessing codebase reusability
We assessed the reusability of our entire codebase, including application Swift modules and utility scripts.
Application Swift modules
We categorized modules into three categories based on their readiness for reuse.
Category A included modules ready to be reused. These were already well-designed for reuse, such as the main Architecture module and the Design System, mostly from the Libraries directory. Our Design System 4.0, for example, was built specifically to be reusable by other products. Note that "ready to be reused" doesn’t mean "should be reused"—each product can decide this independently.
Category B included modules that needed modifications or refactoring. Some modules under the Group directory were only reusable within the Mercari App and required changes for the Global App. As with Category A, it’s crucial to check if a module is "conceptually" reusable—not just whether its current behavior can be reused—and that it doesn’t contain product-specific domain knowledge. This sometimes requires discussion with company stakeholders or other platform teams.
Category C included modules that couldn’t be reused because they’re conceptually specific to the Mercari App.
Scripts, Bazel configurations, CI flows, etc.
These files and configurations were also initially designed for the Mercari App. To enable multi-product reuse, we split common configurations from product-specific ones. For example, Bazel configuration files and custom rules contained product-specific parameters and needed restructuring to handle multiple products flexibly. Examples of setups we reused include:
- Utility scripts and setups, including build, test, and linting/formatting
- Custom Bazel rules and basic configurations
- CI workflows such as bootstrapping, deployments, and E2E
It’s also important to allow product-specific customization for each setup. For example, as described in Manoj’s post below, we unified the internal Fastlane handling and CI pipelines while allowing Mercari App and Global App to have different submission flows.
How We Deliver Mobile App Updates Faster
External dependency and their version management
"External dependency" refers to third-party dependencies like Firebase. If a new product wants to use the same dependency with the same version, it can be reused directly. However, that’s not always the case. When ProductA and ProductB both use Firebase, they might want to:
- Use different versions.
- Apply different patches.
- Have different build configurations.
Our principle is to use the same setup for each dependency whenever possible, while allowing different setups for each product as needed.
Step 3: Gradual migration
With Steps 1 and 2 complete, we could begin the actual migration process.
A key requirement was maintaining continuity—even before the Global App project officially started, over 50 iOS engineers were actively contributing to the repository. We needed to proceed without halting their work or affecting the functionality.
We approached the migration gradually. It consisted of two main phases: creating the structure from Step 1 and moving Category A modules, then refactoring Category B modules and applying changes to the Mercari App.
Refactoring each module required alignment with stakeholders and had to be done one by one. This wasn’t a short-term effort—we performed these tasks incrementally while developing the Global App in parallel.
Global App design direction in monorepo
Once the structure from Step 1 was reasonably in place, we were ready to start implementing the Global App under the Products directory. Due to space constraints, I can’t cover all design decisions in this post, but I’ll introduce the general approach.
General design
Unless there are specific benefits to warrant deviation, the Global App generally follows the architecture, strategy, and tools of the Mercari App. This approach was chosen to reduce unnecessary costs, align with the strict timeline and limited size of the team, and leverage support and knowledge from our enablement team (also called the "architect team" or "infrastructure team").
For example, we’re using the same approach for the following components:
- Basic architecture based on swiftui-atom-properties
- SwiftUI / Design system
- Common patterns such as dependency injection, navigations, A/B testing, code generation, etc.
However, each product should be able to use a different tech stack as needed. For example, we introduced gRPC for the Global App to interact with the BFF server, and adopted Phrase strings to handle multiple localizations—both of which were new challenges for our iOS team.
Additionally, rather than simply following the Mercari App’s patterns, whenever we identified areas for improvement in the design, we worked to improve them in the Global App. These improvements are sometimes back-ported to the Mercari App, creating a beneficial feedback loop.
Inter-product component reusability
One important consideration was whether to reuse feature-level components from the Mercari App. While Mercari App and Global App are different products with distinct appearances today, they looked much more similar when we started implementation. Additionally, their internal logic could overlap since both products provide marketplace functionality.
However, we established a strict policy: feature-level components should never be reused. Instead, they should be recreated and adjusted according to the Global App’s needs when necessary. This policy is critical because reusing feature components without top-down direction can easily lead to the wrong abstraction—the behavior might diverge between the Mercari App and the Global App in the future, potentially causing unintended changes in one product when modifying the other.
There might be a small number of exceptions where the implementation should go into the shared modules. But in those cases, they need to be discussed and agreed upon with the dedicated teams that own the module.
Benefits and Drawbacks
Like any decision, a monorepo strategy has both benefits and drawbacks. These considerations are specific to our Global App development context. Depending on your situation and organization, different approaches may be more appropriate. Key factors to consider include:
- Organization size and team dynamics: The number of engineers contributing to each product, and whether your environment supports effective cross-team collaboration.
- Timeline and strategic focus: Whether you prioritize long-term stability and scalability, or short-term goals such as achieving product-market fit.
- Hiring and talent strategy: While not directly related to monorepo decisions, if you’re considering separate repositories with completely different tech stacks, this becomes a critical consideration.
Benefits
The major advantages of a monorepo stem from utilizing Mercari App’s existing foundation, knowledge base, and infrastructure.
- Modules reusability—monorepo allows the team to utilize the shared modules, along with the knowledge base that was built and tested during the Mercari App development. We can also benefit from any future improvements. Specifically, at Mercari we have multiple independent backend services already, and it is crucial that clients interact with those. Some of our previous products used to rebuild those, but in our case, we can share those modules in
InHousedirectory. - It’s easier to maintain and keep the code consistency and conventions. Mercari App and Global App often face similar technical issues, allowing us to adopt similar solutions, too.
- By reusing our Bazel infrastructure, we benefit from optimized build efficiency that improves each engineer’s daily development through features like remote caching and remote build execution. We can reuse other infrastructure—such as utility scripts and CI/CD pipelines—while allowing customization for each product’s needs.
- Knowledge sharing is promoted, allowing engineers to learn and discuss best practices across different products. This also allows engineers to switch teams between different products easily in the future.
For reference, our micro-modular architecture results in the binary size of 38.8MB for version 1.17.0, as shown in the Taiwan App Store (== install size).
Drawbacks
Adopting the monorepo structure presents several challenges, particularly concerning independence, scalability, and maintenance.
- The initial migration to reorganize the repository requires upfront effort. However, once the monorepo structure is in place, launching new products becomes significantly easier.
- The structure can lead to less independent development, requiring strict module boundaries and policies to be defined. Changes to shared modules necessitate clear decision, communications, testing, reviews, and QA for all affected products.
- Although our build structure is highly optimized, there could be other scalability concerns—for example, when the repository becomes very large, it can potentially cause longer times for file system operations.
- Organizational complexity—in a big organization, managing permissions for different teams in the same repository can be complex and has additional overhead. Additionally, resources for our enablement team may be strained as they support more products.
- I’ve used "monorepo" to mean "handling multiple iOS products in a single repository." However, some teams or projects might also include backend or Android implementations in the same repository and call that a monorepo. While that approach has its own merits, it may conflict with our iOS-focused monorepo strategy.
Side Benefit: AI Synergy
An unexpected benefit of the monorepo approach was its compatibility with AI agents. Reusing core modules such as Design System led to a similar coding direction across products, using Mercari App modules as context enabled the AI agents to generate code that was more aligned with the team’s desired patterns. This synergy was not anticipated when the monorepo direction was chosen, but it was a secondary benefit that we are receiving today.
Additionally, we have recently been holding regular cross-product iOS AI sessions to discuss better utilization of AI agents on the monorepo. This has generated further benefits, such as sharing Claude Code commands.
Challenges and Future Work
As described in the drawbacks section, adopting a monorepo isn’t a perfect solution, and there are certain challenges we need to tackle.
- As the repository and number of products grow, generating Xcode projects that include “everything” leads to long project generation times, heavy indexing, and local disk pressure. Our enablement team worked to mitigate this by providing the option to scope project generation to specific Bazel targets.
- Global App feature modules never depend on Mercari App feature modules. There are similar policies around the dependency management, but we currently enforce this based on guidelines only. As the number of modules keeps growing, it is necessary to have a system to check this automatically.
- While managing a few additional products shouldn’t be a problem, if we scale to dozens of products, new challenges will likely emerge. We’ll need to strengthen our approach to ensuring that changes to shared modules don’t cause unintended impacts across products.
- We currently use the same Xcode and iOS versions for multiple products, but depending on the product situation, we might need to be able to handle different versions, and shared modules might need to be compatible with all those versions.
Conclusion
In this article, I’ve explained how we built the Global App within Mercari’s iOS monorepo structure. We covered the migration process from a single-product repository to a multi-product monorepo, the design decisions we made for the Global App, and the benefits and challenges of this approach. While the monorepo strategy has proven effective for our needs—enabling us to leverage existing infrastructure, and maintain consistency—it also comes with trade-offs in terms of team independence and maintenance complexity.
Our Global App is the first product in our monorepo approach—we don’t consider the current environment to be perfect, and we expect to face new challenges as we develop future products. However, we’re committed to carefully evaluating the relevant factors for each situation and making the right decisions to guide development.
Thanks for reading. Tomorrow we have Gary’s article.

