Building Capacitor mobile App from Meteor+React

As @harry97 mentioned, one of the things I’ve worked on in the past is integrating Capacitor into my Meteor apps. I work part-time on Meteor core, and the rest of my time is dedicated to creative experimentation and inovation, and hobby projects.

I’d like to take this chance to share more about what I’ve done. Maybe it’ll help you complement or rethink your current approach.

Capacitor can work independently

First, considering how tightly Meteor is integrated with its tooling, my main goal with Capacitor integration was keeping it independent. The idea was to let the Capacitor app evolve on its own, rather than being fully tied to Meteor’s setup.

If you’ve worked on a standalone Capacitor project, you’ll know that you can wrap any existing web app as a native app. Running npx cap add android or npx cap add ios sets up a native project inside your app. When you run npx cap sync, Capacitor compiles whatever is in your webDir config into the Android and iOS projects.

So essentially, any web project with an index.html and referenced assets can be wrapped using CapacitorJS.

Reusing the Meteor Cordova build

When you run meteor run android or meteor run ios in dev mode, Meteor produces Cordova-ready assets in .meteor/local/build/programs/web.cordova. The same applies when running a production build with meteor build but in a different folder.

Inside that folder you’ll find all necessary assets: your public/ files, compiled packages/, app.js code, etc, basically the full Meteor app structure ready for native environments. The only missing file is index.html, which links them.

As you mentioned, that file is dynamically generated via boilerplate-generator. But you can fetch it with a simple curl from http://localhost:3000/__cordova/index.html while your app is running locally. Drop it into your Capacitor webDir, along with the other files.

At first, it’ll break. That’s expected, since paths like __cordova/* won’t resolve. But if you fix those paths to match your webDir structure, the app will start loading.

Once you’ve done that, run your app with npx cap run android or npx cap run ios. You may face some runtime issues. One common one is Meteor code referencing WebAppLocalServer, which doesn’t work since the cordova-plugin-meteor-webapp isn’t compatible with Capacitor. You can patch WebAppLocalServer with a no-op in index.html.

Other fixes might be needed depending on your app. Simpler projects with fewer Cordova dependencies will be easier. Apps that use Cordova dependencies via Meteor’s cordova-plugins file must migrate to full CapacitorJS integration, which supports those plugins, though you’ll need to add them to your project’s package.json instead and ensure its compatibility. But I recommend finding and replacing to the official Capacitor plugin alternative.

Also, configure capacitor.config.json to point to your Meteor server during development, and ROOT_URL in production.

With this setup, you should see your app running in the Capacitor emulator.

Automation is key

Once this works, it’s a big step. But for proper development experience, automation is critical.

You need to run Meteor in Cordova mode. Then, use a watcher to monitor .meteor/local/build/programs/web.cordova, copy the updated files into your webDir, fetch and patch the index.html, and let Capacitor sync changes.

You’ll also want a script to prepare the Capacitor app for production, based on the output of meteor build.

What’s missing?

The app will behave like a Cordova app, since it’s using the same build and server runtime.

You might ask about HCP. Since cordova-plugin-meteor-webapp doesn’t work with Capacitor, you’ll need another solution. CapacitorJS provides its own local server, so Meteor’s is no longer required or maintained. The good news is you can skip HCP or use a Capacitor-native approach. I personally use a manual, self-hosted HCP setup with @capgo/capacitor-updater, but they also offer a hosted service.

As for production, once you’ve built your Android and iOS projects, use Android Studio and Xcode to distribute them, same as with Meteor Cordova apps (docs here).


I’ve been using CapacitorJS for long time. I’ve used plugins like camera, paywall, barcode scanner, HCP, and others without issues. It’s fully compatible with both Android and iOS.

Early on I faced problems due to Node 14 limits in Meteor 2. To use newer Capacitor versions (requiring latest Node), I ran them on a standalone Node setup. With Meteor 3 and its updated Node, this won’t be a problem anymore.

As you know, CapacitorJS is on our roadmap. I’d be happy to help automate this approach at the core level. Right now, our focus is on speeding up the bundler and integrating a modern one, unlocking features like tree shaking, exports in package.json, ESM, plugins, and more. I’ve already explored this in my hobby project, and now it’s time to bring it in. Once that’s done, the CapacitorJS experience will benefit too, especially considering how slow the current Cordova build process is.

Hopefully this inspires some of you to experiment or extend your current setup.

I have a working approach and would like to automate it for the core. Fortunately, Meteor 3.3 will include core changes for CapacitorJS, so I can experiment with the approach explained here via a core package sooner than expected.

6 Likes