Nx and Next.js: the deployment edge I hit in a monorepo

The first useful Nx demo is easy to understand: put applications and libraries in one workspace, share the tooling, and let the project graph show what depends on what. The first Vercel deployment is where the shape of the repository starts to matter.
That was the part of Nx I tried seriously for React and Next.js work. The generators were useful, and the graph made a growing repository easier to see. The rough edges were around the boundary between a monorepo tool and a hosting platform, where a local command can succeed while the deployment service starts from a different directory.
One update is important before reading the old screenshot: the Vercel setting described in the original version of this post was a historical workaround. The current Nx Vercel guidance says to leave the Root Directory empty so Nx can run from the full repository root.
Decide whether the workspace needs Nx
For one small Next.js app, a plain repository has fewer moving parts. Nx starts to earn its place when several apps share libraries, when CI should run only the projects touched by a change, or when task dependencies and caching are valuable enough to maintain.
The current Nx docs list the @nx/next plugin as supporting Next.js >=15.0.0
and <17.0.0. That is a version matrix, not a promise that every future Next.js
release will work unchanged, so check it when choosing versions.
The walkthrough below targets Nx 23. The local validation environment was
Node.js v24.14.0 with pnpm 11.9.0; that is the test baseline for this article,
not a claim that Nx supports every Node or pnpm release.
Start from a workspace
For a new workspace, Nx's built-in next preset is the shortest route to a
working shape. These flags make the example non-interactive and keep the
generated workspace on pnpm without adding a remote cache or an initial Git
repository:
pnpm dlx create-nx-workspace@23 acme --preset=next --packageManager=pnpm --nxCloud=skip --skipGit --interactive=false --aiAgents=none
cd acme
pnpm exec nx show projectsIf a Next.js repository already exists, add Nx incrementally instead of moving files first:
pnpm dlx nx@23 init
pnpm exec nx add @nx/next@23
pnpm exec nx report
pnpm exec nx show projects --with-target=buildThe @23 suffix keeps the CLI, core package, and official Next.js plugin in the
same major. Nx publishes those packages in lockstep,
and nx add accepts a versioned package specifier.
That leaves patch selection to the v23 range while avoiding a silent jump to a
newer major. The plugin can infer targets from a Next.js configuration file, and
nx show project is a useful way to inspect the result rather than guessing
which command Nx generated.
To create another application and exercise the boundary early:
pnpm exec nx g @nx/next:app apps/web
pnpm exec nx dev web
pnpm exec nx build web
pnpm exec nx start webThe last command serves a production build, so run build first. Shared
libraries can be generated alongside the app and imported through the workspace
package name.
Use the graph where it helps
The graph becomes valuable when the workspace is large enough that running every task on every change is wasteful:
pnpm exec nx graph
pnpm exec nx affected -t build --base=origin/main --head=HEADNx uses Git plus the project graph to identify the projects touched by a change
and their dependents. affected is still a calculation, not a guarantee that a
shared library is cheap to rebuild; a widely used library can make most of the
workspace affected. Remote caching helps when those tasks are repeated.
The Vercel setting changed
The original version of this article showed the Vercel project root set to the app directory:

That is the historical part. In the current Nx v23 documentation, an imported Nx workspace should use the full repository root. The example Vercel settings are:
Root Directory: leave empty
Framework Preset: Next.js
Build Command: pnpm exec nx build web --prod
Output Directory: apps/web/.nextThe build and output fields are the important pair. Nx runs from the repository
root, and Vercel publishes the .next directory produced inside the selected
application. Do not point Root Directory at apps/web just because that is where
the Next.js files live.
That current setup also changes how I think about the old workaround. In the old
project, changing the root meant copying runtime configuration and an app-level
package.json with normal Next scripts:
{
"scripts": {
"start": "next start",
"build": "next build"
}
}Those files explain what the old deployment needed, but they are not a reason to duplicate configuration in a current workspace. Start with the full-root Nx configuration and only add explicit files or targets when the build proves they are required.
There is one cache detail worth making explicit. Next.js embeds NEXT_PUBLIC_*
values into the client bundle at build time. If Nx restores a cached build made
with different values, Vercel can serve a bundle built for the wrong environment.
Add the variables to the build target's inputs:
{
"targets": {
"build": {
"inputs": ["default", "^production", { "env": "NEXT_PUBLIC_API_URL" }]
}
}
}Keep default and ^production when extending the inferred inputs. Add one
env entry for each public variable that affects the build. Nx documents
--skip-nx-cache as a diagnostic escape hatch, but including the environment in
the cache key is the durable fix.
Keep old bug reports in their version
The original project also hit a Storybook generator bug: a required config file was missing for the Next.js integration. The issue was reported in Nx issue #4092 and later patched. That is useful history for someone maintaining the old workspace; it is weak evidence about a current Nx release. Read the matching migration notes before applying a fix from an old issue.
Generators save time while the repository stays on their supported path. Once a workspace has custom scripts and deployment assumptions, those generated files are ordinary code that the team owns.
Connect the first deploy early
I would begin a small product with plain Next.js. For a workspace with multiple applications, shared libraries, and affected builds, Nx can pay for its own complexity. I would connect the first deploy before generating the second app so the repository root, build command, output directory, and cache inputs are known parts of the project rather than surprises at release time.
The generator gives a workspace its first shape. The deployment contract, task graph, and version pins determine whether that shape remains comfortable to own.