Fix: Nx Not Working — project.json Targets, Affected Commands, Caching, and Generators
Quick Answer
How to fix Nx errors — nx.json plugin config, project.json target inputs/outputs, nx affected base branch, cache misses, generator schema, custom executors, and nx migrate failures.
The Error
You run nx build my-app and the project isn’t found:
Cannot find project 'my-app'Or nx affected rebuilds everything even though you only changed one file:
$ nx affected -t build
# Builds all 50 projects instead of just the affected one.Or caching reports a hit but the output is wrong:
> nx run my-lib:build
✓ existing outputs match the cache, left as is
# But dist/ has outdated files.Or nx migrate fails halfway through an upgrade:
Error: An error occurred while migrating.
Run nx migrate --restore to revert.Why This Happens
Nx is a monorepo build system that scales to thousands of projects. Its complexity comes from:
- Two config layers.
nx.jsonis workspace-wide (plugins, target defaults, caching).project.json(per project) defines targets and dependencies. - Graph-based affected detection.
nx affectedcomputes which projects changed since a base ref and only runs targets for them — but the graph must reflect your dependencies. MissingimplicitDependenciesor wronginputsproduces false positives or misses. - Caching by inputs. Each target hashes its inputs (source files, env vars, command). Identical hash → cache hit. Wrong
inputs(missing files, including unrelated ones) breaks caching. - Plugin model. Plugins (e.g.
@nx/next,@nx/react,@nx/eslint) define generators and executors. Plugin version mismatch with Nx core breaks generators.
Fix 1: Set Up nx.json
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/.eslintrc.json"
],
"sharedGlobals": [
"{workspaceRoot}/tsconfig.base.json",
"{workspaceRoot}/.eslintrc.json"
]
},
"targetDefaults": {
"build": {
"cache": true,
"inputs": ["production", "^production"],
"dependsOn": ["^build"]
},
"test": {
"cache": true,
"inputs": ["default", "^production"],
"dependsOn": ["^build"]
},
"lint": {
"cache": true,
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"]
}
},
"defaultBase": "main"
}Three key parts:
namedInputs— reusable input sets.productionexcludes test files.targetDefaults— defaults for targets across all projects.dependsOn: ["^build"]means “before building me, build all my dependencies.”defaultBase— whatnx affectedcompares against by default.
The ^ prefix means “this target on all dependencies.” inputs: ["^production"] means “consider production files in dependencies.”
Pro Tip: Run nx graph to see the inferred dependency graph. If a project that shouldn’t depend on another is connected, your tsconfig.base.json paths or package.json deps imply it. Fix the import or mark implicitDependencies accurately.
Fix 2: Define project.json Targets
Each project (under apps/ or libs/ typically) has a project.json:
{
"name": "my-app",
"sourceRoot": "apps/my-app/src",
"projectType": "application",
"tags": ["scope:web", "type:app"],
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/my-app",
"main": "apps/my-app/src/main.ts",
"tsConfig": "apps/my-app/tsconfig.app.json",
"webpackConfig": "apps/my-app/webpack.config.js"
},
"configurations": {
"production": {
"optimization": true,
"sourceMap": false
}
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"options": {
"buildTarget": "my-app:build"
}
}
}
}Targets like build, serve, test, lint are callable: nx run my-app:build or nx build my-app.
outputs declare what the target produces — Nx caches these. If outputs is missing or wrong, the cache stores nothing useful and rebuilds always.
For inferred projects (Nx 17+), you may not need project.json at all — Nx auto-detects based on plugins:
// nx.json
{
"plugins": [
{
"plugin": "@nx/next/plugin",
"options": { "startTargetName": "start", "buildTargetName": "build" }
}
]
}Run nx show project my-app to see inferred targets.
Common Mistake: Editing inferred targets via project.json. Inferred targets come from the plugin; project.json overrides only specific fields. Use nx show project to confirm what’s effective.
Fix 3: Fix nx affected
nx affected -t build runs build only on projects affected by changes since the base ref:
nx affected -t build --base=main --head=HEAD
# Or use the configured default base:
nx affected -t buildFor CI:
# GitHub Actions:
- run: pnpm exec nx affected -t lint build test --parallel=3Affected detection requires:
defaultBase: "main"in nx.json (or--basearg).- Git history —
nx affectedwalks the git log. Shallow clones miss commits. - Correct dependency graph — if A imports from B, changes to B should mark A as affected.
If nx affected runs nothing despite changes:
- Run
nx print-affected --base=main --head=HEADto see which projects Nx thinks are affected. - Check
nx graph— visualize the dependency graph. - Verify your branch isn’t ahead of
mainin a weird way:git rev-list --left-right main...HEAD.
If nx affected runs everything:
- Likely a shared file in
namedInputs.sharedGlobals(liketsconfig.base.json) changed. - Or a global config (
.eslintrc.jsonat workspace root) changed.
To debug specific affected logic:
nx affected:graph
# Opens a browser visualization of affected projects.Pro Tip: In CI, fetch the full git history. Shallow clones (fetch-depth: 1 in actions/checkout) break nx affected. Use fetch-depth: 0 for full history.
Fix 4: Cache Configuration
Local cache lives in .nx/cache/. Hashes consider:
- File contents matching
inputs. - Target command + options.
- Environment variables (if declared).
- Hash of
package.json+ lockfile.
To debug cache misses:
NX_VERBOSE_LOGGING=true nx build my-app
# Verbose output shows the hash computation.Look for:
- Cache miss because of… — Nx tells you which input changed.
- Cache key: abc123… — record the hash. Re-run and compare; if hash changes between identical inputs, an input isn’t deterministic (timestamps, random IDs).
For env vars affecting the build:
{
"namedInputs": {
"production": [
"default",
{ "env": "NODE_ENV" },
{ "env": "API_URL" }
]
}
}{ "env": "NODE_ENV" } makes that env var part of the cache key.
To clear cache:
nx reset
# Clears local cache. For Nx Cloud, this doesn't affect remote cache.For Nx Cloud (remote cache shared across team and CI):
npx nx connect
# Sets up Nx Cloud for the workspace.This stores cache artifacts in Nx Cloud — CI runs can reuse cache from a teammate’s local build, and vice versa.
Common Mistake: Including too much in inputs. inputs: ["default"] includes test files and configs. Targets that don’t actually depend on those over-invalidate. Use named inputs (production) to scope.
Fix 5: Implicit Dependencies
When project A depends on B but no import shows it (e.g. B is a CLI tool A invokes at build time), declare it:
// apps/my-app/project.json
{
"name": "my-app",
"implicitDependencies": ["cli-tool", "shared-config"]
}implicitDependencies tell Nx “rebuild me if these change,” even without imports.
For library tags + module boundaries:
// nx.json
{
"tags": ["scope:web", "scope:admin", "type:app", "type:lib"]
}// .eslintrc.json
{
"rules": {
"@nx/enforce-module-boundaries": ["error", {
"depConstraints": [
{ "sourceTag": "scope:web", "onlyDependOnLibsWithTags": ["scope:web", "scope:shared"] },
{ "sourceTag": "type:app", "onlyDependOnLibsWithTags": ["type:lib"] }
]
}]
}
}This prevents accidental cross-scope imports (e.g. web app importing from admin libs).
Fix 6: Generators
Plugins ship code generators:
nx g @nx/react:lib my-lib --directory=libs/shared/ui
nx g @nx/next:app my-next-app
nx g @nx/node:lib my-node-libTo customize generation, inspect the generated config and tweak.
For custom generators:
nx g @nx/plugin:plugin my-plugin
nx g @nx/plugin:generator my-generator --project=my-pluginDefine your own scaffolding:
// libs/my-plugin/src/generators/my-generator/generator.ts
import { Tree, formatFiles, generateFiles } from "@nx/devkit";
import * as path from "path";
export default async function (tree: Tree, schema: { name: string }) {
generateFiles(tree, path.join(__dirname, "files"), `libs/${schema.name}`, {
name: schema.name,
});
await formatFiles(tree);
}Run:
nx g @my-org/my-plugin:my-generator --name=fooCommon Mistake: Editing files Nx generators created and then re-running the generator. It usually merges (or asks); but conflicting edits get lost. Generate once, then commit; don’t re-run on existing projects.
Fix 7: Migrations
nx migrate automatically upgrades Nx and its plugins:
nx migrate latest # Pick the latest Nx version
nx migrate @nx/react@21 # Or pin to a specific versionThis generates migrations.json listing required migrations. Apply:
nx migrate --run-migrationsEach plugin’s migrations may rewrite configs, update generators, refactor imports.
If a migration fails:
nx migrate --restore
# Reverts the package.json and removes migrations.json.Then upgrade one plugin at a time:
nx migrate @nx/react@latest
nx migrate --run-migrations
# Verify everything works, then:
nx migrate @nx/next@latest
nx migrate --run-migrationsFor major Nx upgrades (e.g. 16 → 17, 17 → 18), read the Nx blog’s migration guide first — some breaking changes need manual intervention.
Pro Tip: Always commit before nx migrate --run-migrations. The diff is large; you want a clean baseline to compare.
Fix 8: Executors and Custom Build Logic
For tasks Nx plugins don’t cover, write a custom executor:
// libs/my-plugin/src/executors/my-executor/executor.ts
import { ExecutorContext } from "@nx/devkit";
import { execSync } from "child_process";
export default async function (
options: { command: string },
context: ExecutorContext,
): Promise<{ success: boolean }> {
try {
execSync(options.command, {
cwd: context.root,
stdio: "inherit",
});
return { success: true };
} catch {
return { success: false };
}
}In executor.json:
{
"executors": {
"my-executor": {
"implementation": "./src/executors/my-executor/executor",
"schema": "./src/executors/my-executor/schema.json"
}
}
}Use in a project:
{
"targets": {
"custom-task": {
"executor": "@my-org/my-plugin:my-executor",
"options": {
"command": "echo hello"
}
}
}
}For simpler cases, use nx:run-commands (built-in):
{
"targets": {
"deploy": {
"executor": "nx:run-commands",
"options": {
"command": "fly deploy",
"cwd": "{projectRoot}"
}
}
}
}No custom code; just a shell command run via Nx.
Still Not Working?
A few less-obvious failures:
nx affectedignores changes. Check--baseargument vs actual base branch. CI may be in detached HEAD state.- Caching breaks across machines. Differing Node/pnpm versions affect hash. Pin versions via
enginesandpackageManagerin rootpackage.json. - Generator fails with
Could not find Nx workspace. Run from the workspace root, not from inside a project. - Targets disappear after plugin upgrade. Plugin renamed or removed targets. Run
nx show project Xto see current targets; check the plugin’s migration notes. nx servedoesn’t proxy correctly. Plugins may override webpack/Vite proxy config. Checkproject.jsonoptionsfor proxy settings.Cannot find module '@nx/...'. A plugin’s transitive dep is missing.pnpm install(ornpm install) — Nx peer deps need explicit install in some package managers.- Workspace structure tools (apps vs libs). Convention:
apps/for deployable apps;libs/for shared code. Some plugins assume this layout. - Verbose graph rebuilds. Nx caches the project graph in
.nx/cache/project-graph. Stale cache:nx resetto clear.
For related monorepo and build tool issues, see Turborepo not working, pnpm workspace not working, Lefthook not working, and TypeScript path alias not working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Bun Bundler Not Working — Targets, Format, Externals, Macros, and Code Splitting
How to fix bun build errors — target (browser/bun/node) mismatch, format esm/cjs/iife, externals not respected, Bun macros at compile time, splitting and chunks, plugin API, and Bun.build vs CLI.
Fix: Bun Shell Not Working — $ Template Quoting, Pipes, Exit Codes, and Cross-Platform Scripts
How to fix Bun Shell errors — $ template auto-escape vs raw strings, piping with pipe() vs |, throws on non-zero exit, cwd/env scoping, glob expansion differences, and Windows path handling.
Fix: ESLint Flat Config Not Working — eslint.config.js, ignores, Plugins, and Migration
How to fix ESLint flat config errors — eslint.config.js not found, .eslintrc.json ignored after upgrade, ignores replacing .eslintignore, plugin object form, typescript-eslint integration, monorepo configs, and ESLINT_USE_FLAT_CONFIG.
Fix: Node.js Test Runner Not Working — node --test, TypeScript, Mocks, Coverage, and Watch Mode
How to fix Node.js built-in test runner errors — node --test not finding files, ESM vs CJS imports, TypeScript with --experimental-strip-types, mock.method isolation, coverage reporting, and watch mode setup.