If you are trying to rsync exclude node_modules on Mac, you are already solving the right problem. A JavaScript project can contain a few hundred source files and tens of thousands of dependency files. Copying all of them to an external drive, NAS, or cloud-synced folder makes backups slower, noisier, and easier to break.
Rsync exclude node_modules on Mac: the safe default
The shortest useful command is simple:
rsync -av --delete \
--exclude 'node_modules/' \
~/Developer/my-app/ /Volumes/Backup/my-app/
That command copies the contents of ~/Developer/my-app/ into /Volumes/Backup/my-app/, deletes destination files that no longer exist in the source, and skips every directory named node_modules anywhere in the transfer path. For many Mac developer backups, that one exclusion turns a painful sync into a reasonable one.
But there are two details worth slowing down for. First, the trailing slash on the source matters. ~/Developer/my-app/ means “copy the contents of this directory.” Without the slash, rsync may create a nested my-app directory at the destination. Second, --delete is useful only when you truly want the destination to mirror the source. If you are experimenting with rules, start with a dry run:
rsync -avn --delete \
--exclude 'node_modules/' \
~/Developer/my-app/ /Volumes/Backup/my-app/
The -n flag prints what would happen without changing the destination. Keep it in the command until the output matches your intent.
Why node_modules makes rsync slow on Mac
rsync is efficient, but it still has to inspect the file tree. A typical node_modules directory contains package metadata, JavaScript files, TypeScript declarations, source maps, nested dependencies, binaries, postinstall output, and sometimes test fixtures. Even when many files are small, each one has metadata: path, size, modification time, mode, and sometimes extended attributes.
On macOS, that file churn can trigger extra work outside rsync itself. Spotlight may index the destination. Antivirus or endpoint tools may scan copied files. If the destination lives in iCloud Drive, Dropbox, Google Drive, or OneDrive, every new dependency file can also become a cloud upload candidate. The copy command finishes, but the Mac keeps working through a queue of generated files you did not need to preserve.
That is why a developer backup strategy should start with exclusions. Source files, lockfiles, configs, migrations, scripts, docs, and assets are durable. node_modules is a cache produced from package.json and a lockfile. If the lockfile is present, you can recreate the dependency tree with npm ci, pnpm install --frozen-lockfile, or yarn install --immutable.
The best rsync exclude patterns for Node projects
For a real Node.js or frontend project, node_modules is only the first folder to exclude. Build tools create caches and output directories that can be just as noisy. A practical Mac backup command usually looks more like this:
rsync -av --delete \
--exclude 'node_modules/' \
--exclude '.npm/' \
--exclude '.pnpm-store/' \
--exclude '.yarn/cache/' \
--exclude '.next/' \
--exclude '.nuxt/' \
--exclude 'dist/' \
--exclude 'build/' \
--exclude 'coverage/' \
--exclude '.turbo/' \
--exclude '.vite/' \
~/Developer/my-app/ /Volumes/Backup/my-app/
Those rules are intentionally directory-shaped. The trailing slash tells rsync you mean directories, not random files with similar names. Quoting the patterns prevents your shell from expanding anything before rsync sees it.
If you work across multiple stacks, add the common generated folders for those ecosystems too:
- Python:
.venv/,venv/,__pycache__/,.pytest_cache/,.mypy_cache/,.ruff_cache/ - Ruby:
vendor/bundle/,tmp/,log/,.bundle/ - Rust:
target/ - JVM and Android:
.gradle/,build/,out/ - Editors and macOS:
.DS_Store, temporary workspace indexes, and local IDE files you do not intentionally share
Be careful with .git/. Excluding it makes sense when your branches and history are already pushed to a remote and the backup is meant to be a clean working tree. If you rely on unpushed branches, local tags, or reflog recovery, either include .git/ or make a separate Git-level backup.
Use an exclude file instead of a giant command
Once the command grows past a handful of patterns, move the rules into a file. Create ~/Developer/.rsync-dev-excludes:
node_modules/
.npm/
.pnpm-store/
.yarn/cache/
.next/
.nuxt/
dist/
build/
coverage/
.turbo/
.vite/
.venv/
venv/
__pycache__/
.pytest_cache/
vendor/bundle/
target/
.gradle/
.DS_Store
Then reference it from rsync:
rsync -av --delete \
--exclude-from "$HOME/Developer/.rsync-dev-excludes" \
~/Developer/my-app/ /Volumes/Backup/my-app/
This is easier to audit, easier to reuse, and less likely to break when you paste the command into a note or a scheduled job. It also makes code review possible if your team shares backup scripts for project templates.
Common rsync exclude mistakes on Mac
Mistake 1: using a leading slash without meaning to
In rsync filters, /node_modules/ is anchored to the transfer root. That can be useful, but it will not match apps/web/node_modules/ inside a monorepo. If you want to skip dependency directories anywhere in the project, use node_modules/ without the leading slash.
Mistake 2: testing with --delete enabled
--delete removes files from the destination when they are missing from the source. That is correct for a mirror, dangerous for a first draft. Use -n, read the output, and only then remove the dry-run flag.
Mistake 3: copying raw projects into a cloud folder
Syncing from ~/Developer to an iCloud Drive destination can be reasonable if the destination is filtered. Syncing an active project directly inside iCloud Drive is a different thing. Every package install can create thousands of file provider events, and iCloud has no project-aware exclusion system. Keep active projects local, then sync a clean copy.
Mistake 4: ignoring macOS permissions
If a scheduled rsync job works in Terminal but fails under launchd, check permissions. Terminal, your scheduler, or the wrapper app may need access to removable volumes, Desktop/Documents folders, or other protected locations. The symptom often looks like a missing path, but the underlying issue is macOS privacy control.
When to use Lsyncer instead of maintaining rsync scripts
A clean rsync script is a solid solution. If you enjoy maintaining it, keep it. The trouble starts when the script becomes a small backup system: multiple source folders, different destinations, custom exclusions per stack, schedules, logs, mounted-drive checks, and stale-run detection.
Lsyncer is built for the everyday Mac version of that problem. It is a native macOS folder sync app for developers who want filtered local sync without babysitting shell scripts. The app is designed around common developer exclusions such as node_modules, .git, virtual environments, build output, and caches. You choose the folder pair, keep the rules visible, and see when the last sync ran.
It is not a replacement for every rsync workflow. If you need remote SSH sync to Linux servers, use rsync. If you need clean local project backups to another folder, external drive, or cloud-safe destination, Lsyncer is the less brittle option. It is a one-time $19.99 App Store purchase, not another developer subscription.
Best practices for Mac developer backups
- Keep active projects outside iCloud Drive. Use
~/Developer,~/Code, or another local folder for daily work. - Back up the lockfile, not the installed dependency tree. Preserve
package-lock.json,pnpm-lock.yaml, oryarn.lock; skipnode_modules. - Use dry runs before destructive mirrors. Add
-nwhenever you change exclusions or destinations. - Make exclusions explicit. Whether you use
--exclude-fromor a GUI app, the ignored folders should be visible and reviewable. - Check that scheduled syncs actually ran. A backup that quietly stopped three weeks ago is worse than one that fails loudly.
Related reading
- Best rsync alternative for Mac developers — when scripts stop being the easiest way to keep code folders backed up.
- Mac sync two folders — compare Finder,
ditto,rsync, and filtered sync apps for developer projects. - Why iCloud freezes when syncing
node_modules— the cloud-sync version of the same dependency-tree problem.
FAQ
Does --exclude 'node_modules/' skip nested node_modules folders?
Yes. Without a leading slash, the pattern can match directories named node_modules at different levels of the transfer. That is usually what you want for monorepos and workspaces.
Should I exclude .git when using rsync?
Only if your Git history is safely stored elsewhere. If the project is pushed to a remote and the backup is meant to be a clean working copy, excluding .git/ is reasonable. If you need local branches, tags, or reflog recovery, include it or back it up separately.
Is it safe to use --delete with rsync on Mac?
It is safe when the source and destination are correct and you want a mirror. It is unsafe when you are still testing paths or filters. Use -n for a dry run before removing files from the destination.
Can I put the rsync destination inside iCloud Drive?
You can, but keep the destination filtered. Do not copy raw active projects with node_modules, build caches, and virtual environments into iCloud Drive. A clean source backup is far less likely to create a stuck cloud sync queue.
What is the easiest alternative if I do not want to maintain rsync scripts?
Use a developer-aware folder sync app such as Lsyncer, especially for recurring local backups. You get visible folder pairs, reusable exclusions, and sync status without turning a shell command into a private backup system.