Next.js, Prisma & TRPC: Is it Worth Migrating to Pnpm?

Note
This article was last updated on 2023-06-18, the content may be out of date.

It’s common sense to use npm or yarn since they are not just stable, but also widely popular (not without downsides of course). But there are always other options.

There is a small project written in Next.js with Prisma, TRPC with addition of some other APIs (everything is following t3 stack). The package manager chosen initially was npm. Maybe partially due to a shorter cli command than yarn (subjective due to human laziness).

But let’s put the stability approach to a side and pay attention to the lots of hype about pnpm package manager. It’s possible that we might benefit from migrating this small project to pnpm.

Based on multiple user feedback typing pnpm has never been comfortable, and it’s hard to say if it would ever be, but let’s get over this painful moment of painnpm.

Worst case worse we’ll map it to an alias p (such an acceptable terrible idea):

# ~/.zshrc | ~/.bashrc 
alias p=pnpm

The initial assumption has been that IntelliJ Idea integration would be an issue, but it actually worked well. There was a possibility to change Node.js package manager to pnpm in Settings. Run/Debug configuration had this option as well.

/next-js-prisma-trpc-is-it-worth-migrating-to-pnpm/intellij-idea-pnpm.png

/next-js-prisma-trpc-is-it-worth-migrating-to-pnpm/intellij-pnpm-debug-profile.png

This project is using Prisma ORM, and as soon as it was switched to pnpm it started showing issues with the local client that’s supposed to be located in node_modules/.prisma directory, which was not there.

/next-js-prisma-trpc-is-it-worth-migrating-to-pnpm/prisma-pnpm-issue.png

After further research it turned out pnpm’s main feature is restructuring (hoisting) directories under node_modules folder in order to optimize caching and faster access to already downloaded modules. This resulted to the sitation that Prisma simply couldn’t find it. It has been discussed online that this has been a poor choice of Prisma design, which I partially agree with. Nevertheless, it was possible to work around the issue and add prisma package to an exception list and disallow pnpm to hoist it.

As a result, .npmrc file with the following contents has been created:

# .npmrc
public-hoist-pattern[]=*prisma*
node-linker=hoisted

pnpm i succeeded!

When the time came to benefit from the caching and run pnpm i for the second time, it failed with an issue of compiling a native package bufferutil with node-gyp. Research shown pnpm has an ongoing opened Github issue with node-gyp. This issue has been opened since Nov 6, 2019(!).

❯ pnpm i
Lockfile is up to date, resolution step is skipped
Packages: +645
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
node_modules/@prisma/client: Running postinstall script...
node_modules/bufferutil: Running install script, failed in 327ms
node_modules/bufferutil install$ node-gyp-build
│ node:internal/modules/cjs/loader:998
│   throw err;
│   ^
│ Error: Cannot find module '/Users/dmitry/dev/nutcorp/node-gyp-build@4.6.0/node_modules/node-gyp-
│     at Module._resolveFilename (node:internal/modules/cjs/loader:995:15)
│     at Module._load (node:internal/modules/cjs/loader:841:27)
│     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
│     at node:internal/main/run_main_module:23:47 {
│   code: 'MODULE_NOT_FOUND',
│   requireStack: []
}
│ Node.js v18.12.1
└─ Failed in 327ms at /Users/dmitry/dev/nutcorp/squirrelspace/node_modules/bufferutil
node_modules/@prisma/engines: Running postinstall script...
node_modules/esbuild: Running postinstall script...
node_modules/keccak: Running install script...
 ELIFECYCLE  Command failed with exit code 1.
node_modules/prisma: Running preinstall script...

We can see that pnpm is trying to find node-gyp-build somewhere else.

pnpm/pnpm Issue #2135

pnpm/pnpm Issue #2135

pnpm causes `node-gyp rebuild` failures · Issue #2135 · pnpm/pnpm

Read more...

There seemed to be workarounds, and one of them applied: locking down node-gyp to an older version just for bufferctl package:

// package.json
    "pnpm": {
      "packageExtensions": {
        "better-sqlite3": {
          "dependencies": {
            "node-gyp": "7"
          }
        }
      }
    }

It didn’t yield much results, as another issue came up with a different package, after which it was hard to rationalize the time required for fixing current issues, as npm speed was still sufficient. The exploration of pnpm had to be stopped, but hopefully some day everything would work with minor issues.

Per another suggestion on Twitter an additional tweak was added to .npmrc:

It has been set in addition to all other parameters. The final file looked like this:

public-hoist-pattern[]=*prisma*
node-linker=hoisted
child-concurrency=1

Which didn’t yield any different results and a recurring pnpm i resulted into the same error of not finding node-gyp

This migration attempt has been performed for an internal project with only one person on the dev team, but even then it was quite cumbersome and it’s hard to imagine supporting an extensive list of exclusions long-term.

Objectively, npm packages on this project (and other internal projects, to be fair) are not installed so often and there seems to be very little benefit from a faster resolution/caching logic (unless it’s related to CI, of course). So as a result, it’s not worth chasing the performance as much. It’s entirely possible that this idea is biased (and possibly completely wrong, so it would be great to hear more opinions).

As a conclusion, pnpm is a smart improvement over npm or yarn in terms of storing the packages, but it’s hard to justify moving to it yet and should not be recommended as a primary package manager for mainstream projects.

Second Head Post
This is a post from Second Head. So please, don’t expect too much.

Related Content