RORK LABJP
MAX — Rork Max is a separate line from the original Rork. It generates native Swift rather than React Native and compiles on a cloud Mac fleetREACH — It covers iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage, reaching AR/LiDAR, Metal 3D, Dynamic Island, Live Activities, HealthKit, NFC, and Core MLCHOICE — So the decision works backwards from the OS features you need: the original Rork if React Native gets you there, Max if it does notFUNDING — Rork raised a $15M seed led by Left Lane Capital on April 9, and acquired the app builder Paperline around the same timeTRACTION — Max reached $1.5M ARR within three days of its February launch, and the company has signalled it will keep acquiring to bring in engineering talentREALITY — Still, one-click App Store publishing is a figure of speech. Review, certificates, screenshots, and age ratings remain steps you do by handMAX — Rork Max is a separate line from the original Rork. It generates native Swift rather than React Native and compiles on a cloud Mac fleetREACH — It covers iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage, reaching AR/LiDAR, Metal 3D, Dynamic Island, Live Activities, HealthKit, NFC, and Core MLCHOICE — So the decision works backwards from the OS features you need: the original Rork if React Native gets you there, Max if it does notFUNDING — Rork raised a $15M seed led by Left Lane Capital on April 9, and acquired the app builder Paperline around the same timeTRACTION — Max reached $1.5M ARR within three days of its February launch, and the company has signalled it will keep acquiring to bring in engineering talentREALITY — Still, one-click App Store publishing is a figure of speech. Review, certificates, screenshots, and age ratings remain steps you do by hand
Articles/Dev Tools
Dev Tools/2026-09-07Intermediate

Adding .easignore stops EAS from reading .gitignore — count what actually ships to the build

EAS Build decides what to upload from .gitignore, and the moment you add .easignore the two swap places. Here is how I count the bundled files before sending, plus a measured result: once a parent directory is excluded, an exclamation mark cannot bring a file back.

EAS Build18Expo202easignoregitignore2Rork555

I was rebuilding one of my wallpaper apps on EAS late one evening. The build came back green and the artifact downloaded without complaint. Then I opened it on a real device, and Firebase initialization failed quietly, seconds after launch.

The same code ran fine in my local development build. I walked back through the diff for half an hour before it landed: I had put android/app/google-services.json in .gitignore. Keeping it out of history was the thought that came first, and I had forgotten that the build machine still needs it.

EAS Build packs up your project and ships it to the cloud. What decides what does not go is, by default, your .gitignore. A file you decided not to commit is also a file the builder never sees.

.gitignore answers what stays out of history. .easignore answers what reaches the build machine. They share a syntax, which makes them look like the same list, but the question underneath each one is different.

The two lists replace each other — they do not add up

To fix that initialization failure I created a .easignore. I wrote docs/ and dist/ into it, and deliberately did not write google-services.json. My reading was that the native config would now pass through.

It did. Firebase initialized.

The next build's upload also got noticeably slower, because node_modules was going up in full.

.easignore is not added to .gitignore. The moment it exists, it is read instead of .gitignore. Everything you had in .gitignorenode_modules/, .expo/ — stops applying unless you copy it across. If you write the file thinking of it as "a few extra exclusions," this is where it catches you.

Count the bundle before you send it

Noticing this after the upload costs you a full build cycle. So I started counting first. Borrowing git's own matcher takes a handful of lines.

#!/bin/sh
# Pick the single rule file that actually applies, then count what ships
RULES=.gitignore
[ -f .easignore ] && RULES=.easignore
echo "rules in effect: $RULES"
 
# -o lists untracked files; --exclude-from reads only the file you name
git ls-files -o --exclude-from="$RULES" | wc -l
git ls-files -o --exclude-from="$RULES" -z | xargs -0 -r du -cb | tail -1
 
echo "--- things you do not want in / things you cannot afford to lose ---"
git ls-files -o --exclude-from="$RULES" \
  | grep -E 'google-services|GoogleService-Info|(^|/)\.env|node_modules/|(^|/)\.expo/'

The key part is --exclude-from. With that flag git does not pull in the worktree's .gitignore, so you get an honest local preview of the world after .easignore exists.

I ran three rule sets against a representative project layout — src/ and assets/ plus node_modules/, .expo/, dist/, docs/, .env, and two native config files, 18 files in total. The ignore files themselves are left out of the counts.

Rules appliedBundledTotal sizeWhat happens
A .gitignore only12 files5.20 MiBgoogle-services.json and GoogleService-Info.plist never arrive
B .easignore with only docs/ and dist/16 files6.82 MiBnative config arrives, but so do node_modules, .expo and .env
C rewritten .easignore13 files4.53 MiBnative config arrives; the heavy things and .env stay out

B is 1.62 MiB heavier than A. At this scale the number itself looks like noise — what matters is that the extra weight is node_modules and .env. The builder reinstalls dependencies anyway, so shipping them buys nothing. And .env is not merely unnecessary; it is a file I would rather not send at all. How I pass those values instead is in EAS secret visibility does not keep a value out of your app.

C comes out 0.67 MiB below A, which is simply docs/ dropping off. I added what the build needed and the archive still got lighter.

Once the parent is excluded, an exclamation mark will not bring it back

Writing C, my first attempt looked like this:

android/
!android/app/google-services.json

Drop the whole android/ tree, since prebuild regenerates it, and pull one config file back. It felt like the obvious move. It does not work. Here is what the same matcher reported:

How it is writtengoogle-services.json is
android/ plus !android/app/google-services.jsonstill excluded — it never arrives
android/*!android/appandroid/app/*!android/app/google-services.jsonbundled

When you exclude a parent directory outright, no exclamation mark inside it can call a file back, because git stops descending at the directory. To bring something back you have to reopen each level on the way down. Writing !node_modules/react/index.js under an excluded node_modules/ was ignored in exactly the same way.

This is where I lost the most time — an exception you believe you wrote simply does nothing, and nothing tells you so.

How I lay out my own .easignore

These days I write it in three blocks with different purposes. The order carries no technical weight, but it stops me hesitating when I read the file again months later.

# 1. Sent but never used — the builder regenerates these
node_modules/
.expo/
ios/Pods/
android/.gradle/
dist/

# 2. Things I do not want to send — values live in EAS environment variables
.env
.env.*

# 3. Simply heavy — design files and source material
docs/
design/

# 4. Native config files are deliberately absent here.
#    They stay in .gitignore, and they pass through to EAS.

The fourth block records something I did not write. Left blank, a future version of me reads the file, assumes it was an oversight, and adds the line back. One sentence of reasoning removes that doubt.

I wrote up the matching inventory work on the environment variable side in There is no --json on eas env:list. Deciding whether android/ is safe to drop wholesale rests on the process in Find the native edits expo prebuild will erase before you upgrade to SDK 57.

One small thing before your next build

There is only one thing I would suggest trying. Drop those few shell lines into scripts/ and run them before you type eas build. If the numbers match what you expected, send it and carry on.

The first day I ran it, I found node_modules sitting in the list. Learning that after waiting out a full build, versus learning it three seconds before sending, makes a real difference to what is left of your afternoon.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-08-20
A beta-SDK build can reach TestFlight, but it can't reach review
Builds made with a beta Xcode can be distributed through TestFlight, but they cannot be submitted for App Store review. Here is how to check which SDK produced your build, and how to protect your release profile in eas.json.
Dev Tools2026-05-06
Adding Expo Dev Client to Your Rork App
The moment you add react-native-mmkv or RevenueCat to a Rork app, Expo Go stops launching it. Here's how to set up Expo Dev Client (a custom development build) and the three pitfalls I've actually walked into.
Dev Tools2026-04-27
Don't Ship Rork Apps With an Empty EXPO_PUBLIC_RORK_AUTH_URL — A Practical Setup Walkthrough
What to put in EXPO_PUBLIC_RORK_AUTH_URL when Rork generates an app with login, and how to wire it up across dev, EAS Build, and production safely.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links