← Back to posts Cover image for Building Scalable Flutter Apps: A Practical Guide to Multi-Package Monorepos with Melos

Building Scalable Flutter Apps: A Practical Guide to Multi-Package Monorepos with Melos

· 5 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

Taming the Beast: How to Scale Flutter Apps with Melos and a Monorepo

You started with a single, tidy lib/ folder. Then you added a few features, a couple of third-party integrations, and some custom widgets shared between screens. Suddenly, your Flutter project feels like a sprawling city with tangled wires and no zoning laws. Building from scratch takes forever, dependency updates are scary, and onboarding a new developer requires a two-hour tour.

This is the classic scaling problem. The solution? A multi-package monorepo. Instead of one giant app, you break it into logical, independent packages: one for your core business logic, another for your UI components, another for authentication, and so on. Your main app becomes a lightweight integrator, pulling these modules together.

The benefits are huge:

  • Clear Boundaries: Enforces a clean architecture by physically separating concerns.
  • Independent Development: Teams can work on different packages with minimal conflict.
  • Faster Builds: You can rebuild only the package you changed.
  • Reusability: Share packages across multiple apps (e.g., a Flutter and a Dart CLI tool).

But managing this manually is a nightmare. Running flutter pub get in a dozen directories, linking local packages, orchestrating scripts—it’s tedious. This is where Melos comes in.

What is Melos?

Melos is a Dart/Flutter monorepo management tool. Think of it as your project’s command center. It lets you run commands across all your packages simultaneously, manage dependencies between them, and define custom workflows. It’s the glue that makes a monorepo not just possible, but pleasant.

Setting Up Your Scalable Foundation

Let’s build this from the ground up. We’ll also use FVM to lock our Flutter SDK version, a critical practice for team consistency.

Step 1: Initialize the Workspace First, create your project root and set up FVM.

# Create your project directory
mkdir my_scalable_app && cd my_scalable_app

# Use FVM to pin a specific Flutter version
fvm use 3.22.0

Step 2: Install and Configure Melos Add Melos globally and create its configuration file, melos.yaml.

dart pub global activate melos
# melos.yaml at the project root
name: my_scalable_app

packages:
  - "apps/**" # Your main Flutter application(s)
  - "packages/**" # Your shared packages

command:
  bootstrap:
    # This is the magic line. It links internal packages and runs pub get.
    after: |
      melos run hooks:build_runner
  run:
    concurrency: 5

scripts:
  hooks:build_runner:
    description: "Run build_runner for packages that need it"
    run: |
      melos exec --scope="*_api" -- \
        flutter pub run build_runner build --delete-conflicting-outputs

Step 3: Create Your Package Structure Now, create the modular structure. A classic, scalable setup looks like this:

my_scalable_app/
├── melos.yaml
├── apps/
│   └── my_app/           # The main Flutter application
│       ├── pubspec.yaml
│       └── lib/main.dart
└── packages/
    ├── core/             # Pure-Dart business logic, models, interfaces
    │   └── pubspec.yaml
    ├── authentication/   # Auth-related logic and services
    │   └── pubspec.yaml
    └── ui_kit/           # Shared widgets, themes, styles
        └── pubspec.yaml

Step 4: Define Package Dependencies The key is how these packages reference each other. Your apps/my_app/pubspec.yaml and other packages should use path dependencies.

# apps/my_app/pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  core:
    path: ../../packages/core
  authentication:
    path: ../../packages/authentication
  ui_kit:
    path: ../../packages/ui_kit
# packages/authentication/pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  core: # Depends on the core package
    path: ../core

Step 5: Bootstrap and Go! With the structure in place, let Melos handle the heavy lifting.

# From the project root, run:
melos bootstrap

This single command will:

  1. Analyze all your pubspec.yaml files.
  2. Intelligently run flutter pub get (or dart pub get) in each package in the correct order, respecting internal dependencies.
  3. Execute any post-bootstrap hooks (like our build_runner script).

Working in Your Monorepo

Melos makes daily workflows effortless.

  • Run a command everywhere: Need to upgrade a dependency in all packages? Use melos exec -- "flutter pub upgrade".
  • Scope commands: Only run tests in packages that changed: melos run test --scope="packages/*".
  • Custom scripts: Automate anything. Add a script to melos.yaml to generate code, run analyses, or deploy packages.
# Adding a custom script to melos.yaml
scripts:
  analyze:
    run: melos exec -- flutter analyze
  test:all:
    run: melos exec -- flutter test

Run them with melos run analyze.

Common Pitfalls to Avoid

  1. Circular Dependencies: This will break your build. If package_a depends on package_b, package_b cannot depend on package_a. Design your package hierarchy thoughtfully, with a clear downward flow (e.g., app -> feature -> core).
  2. Over-Engineering: Don’t create a package for every three widgets. Start modestly—perhaps just core and ui_kit—and extract new packages when a module has a clear, distinct responsibility and is used in multiple places.
  3. Forgetting the Hook: Remember that melos bootstrap doesn’t automatically run build_runner. Use the after hook (as shown above) or a custom script to handle code generation.

By adopting a Melos-powered monorepo, you’re not just organizing code; you’re building an infrastructure that grows gracefully with your application and your team. It adds an initial setup cost but pays massive dividends in developer velocity, code clarity, and long-term maintainability. Give it a try on your next medium-to-large project—you won’t look back.

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Flutter for High-Performance Desktop: Is it Ready for CAD, Image Processing, and Complex GUIs?

Flutter for High-Performance Desktop: Is it Ready for CAD, Image Processing, and Complex GUIs?

Developers are curious about Flutter's capabilities beyond typical business apps, especially for demanding desktop applications like CAD/CAM or image/video processing. This post will explore Flutter's suitability for high-performance, viewport-based desktop GUIs, discussing Dart's memory model, the 60fps update loop, and real-world examples to gauge its readiness for 'serious' complex software.

Cover image for Debugging Flutter Web Navigation: Solving the Deep Link Refresh Bug

Debugging Flutter Web Navigation: Solving the Deep Link Refresh Bug

Flutter web applications often suffer from a frustrating 'deep link refresh bug' where refreshing the browser on a nested route (e.g., /home/details) bounces the user back to the root or an incorrect path. This post will diagnose the common causes of this issue, explain how Flutter's router handles web URLs, and provide practical solutions and best practices for building robust, refresh-proof navigation in your Flutter web apps.

Cover image for Mastering Internationalization in Flutter: Centralized Strings for Scalable Apps

Mastering Internationalization in Flutter: Centralized Strings for Scalable Apps

As Flutter applications grow, managing strings for multiple languages or just keeping text consistent becomes a challenge. This post will guide developers through effective strategies for centralizing strings, implementing robust internationalization (i18n) and localization (l10n), and leveraging tools to streamline the process for small to large-scale projects.