Hello! In our last session, we laid the groundwork for our mobile app by initializing Capacitor and creating the main configuration file, capacitor.config.ts. We paid close attention to the webDir property, ensuring Capacitor knows where to find your compiled React application within the monorepo.
Today, we'll build on that foundation by properly structuring your Turborepo to house the native iOS and Android projects. Instead of placing the native code in the project root, we will create a dedicated mobile workspace. This approach keeps your monorepo clean, organized, and philosophically aligned with Turborepo's model of managing distinct applications. By the end of this lesson, you will have a mobile app within your monorepo, complete with the necessary pipeline scripts to build your web app, sync it to the native projects, and open them in their respective IDEs.
1. The Case for a Dedicated mobile Workspace
In a monorepo, clarity and organization are paramount. While Capacitor's default behavior is to create ios and android folders in the directory where you run its commands, this can lead to a cluttered root in a large project. A more scalable approach, particularly in a Turborepo environment, is to treat the native wrapper as its own application.
We will create a new workspace at apps/mobile. This directory will contain:
- The Capacitor configuration file (
capacitor.config.ts). - Its own
package.jsonfor managing mobile-specific scripts and dependencies. - The generated native iOS and Android projects.
This isolates all mobile-native concerns into a single, well-defined location, making the project easier to navigate and maintain. This structure is a common pattern in professional monorepo setups.

The following article advocates for a similar architecture, which has proven effective for managing complexity in multi-platform projects.
Building Faster with Turborepo, pnpm and Capacitor
This article discusses a practical monorepo architecture that closely mirrors our goal.
Please read the section titled The Architecture. The author explicitly defines an apps/mobile directory for the Capacitor app, demonstrating that this is a recognized best practice for organizing a monorepo that includes a native component.
2. Creating the mobile Workspace
First, let's create the directory for our new workspace and give it a package.json file to make it discoverable by bun and Turborepo.
-
Create a new directory:
apps/mobile. -
Inside
apps/mobile, create a newpackage.jsonfile with the following minimal content. This officially establishes it as a package within your monorepo.// apps/mobile/package.json { "name": "mobile", "private": true } -
Ensure your root
package.jsonincludesapps/*in itsworkspacesarray so thatbunand Turborepo can find this new package. It should already be configured this way from your initial Turborepo setup.
3. Scoping Capacitor to the mobile Workspace
In our last lesson, we created capacitor.config.ts in the project root. To align with our new structure, we'll move Capacitor's home base into our new mobile workspace.
- Delete the
capacitor.config.tsfile from your monorepo's root directory. - Next, we will re-run the
initcommand, but this time we'll usebun's--cwdflag to execute the command from within theapps/mobiledirectory. This ensures the new config file and subsequent native folders are created in the right place.
Execute the following from your monorepo root:
bunx --cwd apps/mobile cap init
The CLI will prompt you for the App Name and App ID as before. The crucial difference is the webDir. Since this command is running from apps/mobile, the path to your web app's build output is now relative to this location. Assuming your web app is at apps/web and it builds to a dist folder, the path will be:
../../apps/web/dist
After the command completes, you will have a new capacitor.config.ts file inside apps/mobile, correctly configured to find your web assets from its new location.
4. Adding Native Platforms
With Capacitor now configured within the mobile workspace, we can add the native platform projects. These will be neatly generated inside apps/mobile.
First, let's add the required Capacitor platform packages as dependencies to our mobile workspace. Again, we use the --cwd flag to target the correct package.json.
bun add @capacitor/ios @capacitor/android --cwd apps/mobile
Now, add the platforms themselves. These commands will create the ios and android project folders inside apps/mobile.
bunx --cwd apps/mobile cap add ios
bunx --cwd apps/mobile cap add android
Your apps/mobile directory should now contain the ios and android folders, representing the native shells for your application. We will delve into the contents of these folders in future lessons.

5. Configuring the Turborepo Pipeline
The final step is to create a streamlined set of scripts to manage the build-and-sync workflow. We'll add scripts to the mobile package and orchestrate them from the root using Turborepo.
1. Add Scripts to the mobile Package
Open apps/mobile/package.json and add the following scripts. These will be the building blocks for our workflow.
// apps/mobile/package.json
{
"name": "mobile",
"private": true,
"scripts": {
"sync": "bunx cap sync",
"open:ios": "bunx cap open ios",
"open:android": "bunx cap open android"
},
"dependencies": {
"@capacitor/android": "^6.0.0",
"@capacitor/ios": "^6.0.0"
}
}
2. Define the Pipeline in turbo.json
Now, let's inform Turborepo about these new tasks. Open turbo.json at your project root. We need to define the sync and open tasks and, critically, mark them as non-cacheable. These commands have side effects on the file system (copying web assets, opening an IDE) that Turborepo cannot track, so caching their results would be incorrect.
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"lint": {
// ... your lint config
},
"sync": {
"dependsOn": ["^build"],
"cache": false
},
"open:ios": {
"cache": false
},
"open:android": {
"cache": false
}
}
}
The "dependsOn": ["^build"] for the sync task is a powerful declaration. It tells Turborepo that before running sync on any package, it must first run the build task for all of that package's dependencies. In our setup, this ensures the web app is always built before we attempt to sync its assets to the native projects.
3. Orchestrate with Root Scripts
Finally, let's add convenient high-level scripts to your root package.json. These will be your primary entry points for mobile development.
// root package.json
"scripts": {
// ... your existing scripts
"mobile:sync": "bun turbo sync --filter=mobile",
"mobile:open:ios": "bun turbo open:ios --filter=mobile",
"mobile:open:android": "bun turbo open:android --filter=mobile"
}
Here's what happens when you run bun run mobile:sync from your root:
- Turborepo is invoked to run the
synctask. - The
--filter=mobileflag tells it to target themobileworkspace. - Turborepo checks the
pipelineconfiguration forsyncand seesdependsOn: ["^build"]. - It automatically triggers the
buildscript in yourwebapp workspace first. - Once the web app is built, Turborepo executes the
syncscript within themobileworkspace.
This pattern, where simple, chained scripts are used to manage a workflow, is a useful reference.
Build a Next.js Mobile App from Scratch with Capacitor 8
This guide offers a look at creating package.json scripts for a Capacitor workflow, which we've adapted for our more complex Turborepo setup.
Review the scripts in Step 3. You can see the logic of chaining build and sync, which we've elevated by using Turborepo's dependency graph instead of simple && operators.
Conclusion
Congratulations! You have successfully structured your monorepo to accommodate native mobile development in a clean, scalable way. You created a dedicated mobile workspace, correctly scoped your Capacitor configuration, and generated the native project folders within it. Most importantly, you have defined a powerful and efficient pipeline in Turborepo to automate the process of building your web assets and synchronizing them with your native apps.
Our key takeaways are:
- Creating a dedicated
apps/mobileworkspace isolates native concerns and keeps the monorepo organized. - Using
bunx --cwdallows you to run Capacitor commands within the context of a specific workspace. - The
webDirpath incapacitor.config.tsmust be relative to the config file's location. - Turborepo's
pipelineanddependsOnfeatures enable you to create robust, dependency-aware workflows, like ensuring the web app is built before syncing. - Tasks with external side effects, like
syncandopen, should have caching disabled inturbo.json.
In our next lesson, we will turn our attention back to the web application itself. We'll configure its Vite build process to ensure it generates output that is perfectly optimized for running inside a Capacitor WebView, addressing details like asset paths and routing.
Can't find a good explanation? Sign up and we'll make it for you
Sign up