D
P
0

Next.js

`Module not found: Can't resolve '@sanity/ui'`? A Silent Turbopack Build Failure and a Deploy That Never Landed

August 2, 2026·4 min read
`Module not found: Can't resolve '@sanity/ui'`? A Silent Turbopack Build Failure and a Deploy That Never Landed

There is a class of bug that costs far more than a normal error: the kind that does not look like a bug at all. I lost about three hours to one of these on a client project, and the painful part was not the root cause. It was realising that for those three hours I had been debugging something that never reached the server.

The symptom: everything looked fine

I was working on the glossary navigation inside a Sanity Studio embedded in a Next.js app. I changed a component, committed, pushed. The build ran on Railway. I opened Studio in production to check.

Nothing had changed.

My first instinct was the obvious one: the code must be wrong. So I went back, reviewed the component structure, adjusted it, pushed again. Still nothing. I repeated that loop several times, each round inventing a more elaborate theory for why the navigation refused to appear.

What never crossed my mind: the site I was looking at was not my latest build. The build had failed, and the last healthy version kept being served. From the browser everything looked alive and well. No error page, no 500, no signal at all that the most recent deploy had never landed.

The error was there. I just was not looking at it

When I finally opened the build logs, the error had been sitting there the whole time:

Module not found: Can't resolve '@sanity/ui'

Failing at the Turbopack stage. The build had been broken since 13:21 and I only noticed close to 17:00.

That is the first lesson, and probably the most valuable one: when your change does not show up in production, verify that the build actually succeeded before you blame your code. I skipped that step precisely because the site looked healthy, which is exactly what made it misleading.

The root cause: a transitive dependency I had been freeloading on

Several custom Studio components imported from @sanity/ui:

import { Card, Stack, Text } from "@sanity/ui";

The problem: @sanity/ui was never listed in my package.json. It was installed only because sanity itself depends on it, which makes it a transitive dependency, not a direct one.

That distinction is where dev and production part ways:

  • Dev mode is relaxed about resolution. A module sitting in node_modules because some other package pulled it in still resolves, so my imports worked locally without complaint.
  • Turbopack production builds are not relaxed. They expect anything you import to be declared directly. If it is missing from package.json, resolution fails.

So the exact same code was green locally and dead in production. To make it worse, this was a self inflicted wound. I had removed that dependency from package.json earlier while trying to fix an unrelated Studio loading issue. My diagnosis back then was wrong, and the bill only arrived weeks later as a silently failing build.

The fix

Declare the package directly, using the same version range that sanity declares:

{
  "dependencies": {
    "sanity": "^5.21.0",
    "@sanity/ui": "^3.1.14"
  }
}

Matching the range matters. Pick an arbitrary different version and your package manager can install two copies of @sanity/ui side by side, and Studio will break in ways far more confusing than a failed build. After installing, confirm there is only one instance:

pnpm why @sanity/ui

If the output shows a single deduped version, you are clear.

A diagnostic for stuck deploys

Because this failure mode can return wearing a different costume, I now run a quick check whenever I suspect I am staring at a stale version. Compare the timestamp of the served static assets against your last commit:

curl -sI https://your-site.com/_next/static/chunks/main.js | grep -i last-modified
git log -1 --format=%cd

If Last-Modified is significantly older than your latest commit, you are looking at an old build. Stop debugging the code and go read the build logs.

What I took away

  • A failed deploy does not always take the site down. When the previous version keeps serving, the failure becomes invisible, and that is far more dangerous than an honest error.
  • Every package you import belongs in package.json. Riding on a transitive dependency can work in dev and die in a production build.
  • When adding a package that another package also depends on, match the version range so you do not end up with duplicate instances.
  • Before blaming your code, prove that your code is actually deployed.