TL;DR: Vercel's Node File Trace only bundles files reached through node_modules. Cross-tree relative imports inside /api/ are never packaged into sibling function bundles. The deploy goes green, TypeScript is clean, and every endpoint that imports a shared helper 500s at runtime. Here is how I found it and the only two patterns that work.
I am a solo founder building AimVantage (https://aimvantage.uk), an AI job-prep app on Vercel serverless functions. I wanted one obvious thing: shared helper files for Sentry, email, and audit logging, imported by each API handler. Sane. Normal. DRY.
It cost me five failed strategies.
What I tried (all failed at runtime with ERR_MODULE_NOT_FOUND)
- A top-level lib folder imported via relative paths
- An api/_lib folder (underscore-prefixed)
- An api/shared folder
- A vercel.json includeFiles glob
- Renaming with underscore prefixes to dodge the functions cap
Every one built successfully. Every one showed Ready in Vercel. Every consumer endpoint then 500s on the first real request. tsc --noEmit cannot catch this. The build logs cannot catch this.
What actually works
Inline the helper into each handler. Self-contained functions; NFT only has to follow bare-package imports from node_modules, which it does reliably. The tax: a helper change touches up to six files.
Or use a real workspace package via the package.json file:./packages protocol, so the import resolves through node_modules.
The lesson that saved me
The only thing that caught this was a smoke test hitting the deployed URL and asserting real JSON responses. Not a build check. Not a type check. I now block done on npm run smoke going green against production.
If you run serverless on Vercel and share code across api trees: assume your relative imports are lying to you until a request proves otherwise.
I am building this solo at https://aimvantage.uk, free CV and interview tools with no signup, if you want to see what it is.
Top comments (0)