Vercel & Remix
Vercel's free hobby plan looks convenient: push to deploy, global edge, previews. After a week of testing a Remix app, file handle limits became the actual bottleneck.
Warning
Persistent EMFILE errors (too many open files) appeared only in the Vercel environment, never locally:
Error: EMFILE: too many open files, open '/var/task/node_modules/nanostores/index.js'
at Object.openSync (node:fs:562:18)
at readFileSync (node:fs:446:35)
at getSourceSync (node:internal/modules/esm/load:66:14)
at getSource (node:internal/modules/esm/translators:68:10)
at createCJSModuleWrap (node:internal/modules/esm/translators:185:32)
at ModuleLoader.commonjsStrategy (node:internal/modules/esm/translators:275:10)
at #translate (node:internal/modules/esm/loader:534:12)
at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:581:27)
Node.js process exited with exit status: 1.
Vercel helpfully adds:
The logs above can help with debugging the issue.
That note does not help since you get no visibility into the per-process file descriptor limit.
Remix users hit this more than most. Vercel's serverless runtime is more restrictive than local dev environments, so file handle limits surface only during deployment.
What the Community Reports
Reddit threads: Recurring EMFILE on deploy, often with lucide-react and other icon sets.
GitHub issues: Remix + Vite + large icon libraries (phosphor, heroicons, lucide) trigger failures. Replacing barrel imports with direct paths reduces frequency.
Consensus: This is a platform constraint. Local dev rarely reproduces it. Import patterns influence open-file churn.
The community workarounds:
- Optimizing build-time concurrency
- Refactoring dynamic imports from icon libraries
- Updating dependencies and build tooling
- Switching from barrel to direct imports
These are mitigations, not fixes. They trade code clarity for staying within an unknown cap.
Routing Convention Collision
Caution
Beyond file handle limits, Vercel's filesystem-based routing clashes with Remix conventions.
Remix uses parentheses in filenames for layout routes — like app/routes/(dashboard).profile.tsx to create nested layouts without affecting the URL structure.
Vercel reserves parentheses for its own routing logic. Deploy a Remix app with parenthesized route files and the build process throws errors until you rename them. Your intended routing structure breaks.
Why this happens: Vercel generates serverless functions from your filesystem structure. Each route file becomes a separate endpoint. The platform reserves [] for dynamic segments and () for route groups. Remix uses parentheses for layout composition, not URL generation. The two conventions collide.
The workaround tax: You refactor route organization to avoid parentheses. This means losing the clean layout composition that makes Remix routing elegant. Instead of (dashboard) layouts, you use nested folder structures that don't map as cleanly to your component hierarchy.
So what next?
I am considering Fly.io as an alternative, so stay tuned.