Next.js in a Monorepo: Resolving internal packages

Hi, I am using npm workspaces and turborepo for a web app, which is reliant on a TypeScript Node.js library which I have defined as an internal package in my monorepo.

When I deploy to Vercel, I get this error: my-app:build: Module not found: Can't resolve '@repo/script.js'.

Here’s what the package.json for the script looks like:

{
  "name": "@repo/script",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "build": "npm i && tsc;",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": { /* some npm registry dependencies */ }
  "exports": {
    "./*": "./dist/src/*"
  }
}

and it’s tsconfig:

{
    "compilerOptions": {
        "target": "ES2022",
        "module": "nodenext",
        "moduleResolution": "nodenext",
        "esModuleInterop": true,
        "composite": true,
        "outDir": "./dist",
        "rootDir": ".",
        "preserveSymlinks": true,
    },
    "include": ["src"],
    "exclude": ["dist", "node_modules"],
    "ts-node": {
        "require": ["tsconfig-paths/register"]
    },
}

My Next.js app’s package.json looks like:

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "check-types": "tsc --noEmit"
  },
  "dependencies": {
    "@radix-ui/react-avatar": "^1.1.0",
    "@radix-ui/react-dropdown-menu": "^2.1.1",
    "@radix-ui/react-slot": "^1.1.0",
    "@radix-ui/react-tabs": "^1.1.0",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "lucide-react": "^0.427.0",
    "next": "14.2.5",
    "next-themes": "^0.3.0",
    "react": "^18",
    "react-dom": "^18",
    "tailwind-merge": "^2.5.2",
    "tailwindcss-animate": "^1.0.7"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

and it’s tsconfig

{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "moduleDetection": "force",
    "noUncheckedIndexedAccess": true,
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "preserveSymlinks": true,
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "rootDir": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@repo/script": ["../../lib/script/dist/src/*"],
    },
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"],
  "references": [
    { "path": "../../lib/script" }
  ]
}

I’ve tried including @repo/script as a direct dependency of nextjs-app, but that leads to an error as Vercel thinks it’s in the npm registry and tries to fetch it without success. I’ve also tried including a preinstall command which builds @repo/script, but that also doesn’t work.

Am I just misunderstanding how to use a monorepo with Next.js? I’ve looked at the Turborepo examples and they don’t seem to be doing anything different.

Hi, and welcome to the community, @archie-mckenzie!

Just to confirm - you’re currently not using Turborepo, correct? I’ll share some related docs in case it’s helpful:

With the assumption that you’re not using Turborepo, what does your root package.json look like? As in the npm Workspaces Documentation, you need npm to recognize and link your internal packages. Something like:

  "private": true,
  "workspaces": [
    "apps/*",
    "lib/*"
  ]
}

Could you try that?

Hi @pawlean I am using Turborepo, and my root directory package.json looks like

{
"private": true,
  "workspaces": [
    "packages/apps/*",
    "packages/lib/*"
  ]
}

On my local machine both turbo run build and turbo run dev work fine.

Yeah, that’s a nightmare for me as well !

I’ve tried including @repo/script as a direct dependency of nextjs-app, but that leads to an error as Vercel thinks it’s in the npm registry and tries to fetch it without success.

Their example main workspaces don’t actually include (in package.json files) the dependency workspace, but they seem to be able to import it without having an issue. Which I seriously doubt, how can the file path be resolved then?

I’ve tried various things :

  • Enabling " Include files outside the root directory in the Build Step." on the project settings tab : no difference
  • Importing them with a relative file path : works fine on a local machine with all the reference, but of course not on Vercel, since they are not pushed before the build.
  • Trying to symlink the dependency from inside main package. Kind of ugly but if that can make it work. But no, they are not supported by Vercel at build time : Symlinks are broken in Vercel integration for Github? · vercel/vercel · Discussion #5347 · GitHub
  • Lastly, I used hardlinks which is extra messy with Git seeing it as different files, almost the same as duplicating the files.

That seems so simple in the documentation but actually it is not.
Anyone able to give a hint about this ? Or at least to confirm there is no point in trying.