Skip to content

Nx Integration Guide - DRY Configuration Approach

Date: October 12, 2025
Status: ✅ Active Policy
Branch: feat/nx-integration

This monorepo uses Nx plugins with automatic inference to avoid configuration duplication. This guide explains when to add project.json files and when to rely on automatic detection.


⚠️ Critical Rule: Avoid Over-Configuration

Section titled “⚠️ Critical Rule: Avoid Over-Configuration”

DO NOT create project.json files unless absolutely necessary. Nx plugins automatically detect and configure most projects.

  • DRY Principle: Don’t Repeat Yourself - plugins infer configuration from package.json and file structure
  • Maintenance Burden: Explicit configs must be kept in sync manually
  • Error Prevention: Over-configuration leads to conflicts and drift
  • Plugin Power: Let Nx plugins do their job - they’re smarter than manual config

Nx automatically detects any directory with a package.json file:

Terminal window
packages/theme/package.json Nx detects as "theme" project
packages/hooks/package.json Nx detects as "hooks" project
apps/stay_match/*/mobile/package.json Nx detects as mobile apps

No project.json needed! Nx knows these exist.

Our nx.json configures these plugins that auto-generate targets:

{
"plugins": [
"@nx/expo/plugin", // Auto-detects Expo apps, adds start/build/run-ios/run-android
"@nx/react/router-plugin", // Auto-detects React Router apps
"@nx/vite/plugin", // Auto-detects Vite projects, adds build/serve/test
"@nx/eslint/plugin", // Auto-detects ESLint configs, adds lint target
"@nx/react-native/plugin" // Auto-detects React Native apps
]
}

Each plugin scans the monorepo and adds appropriate targets automatically.

If a project needs minimal metadata (name, tags) but relies on plugin inference:

{
"name": "guestroom_city-mobile",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/guestroom/guestroom_city/mobile/src",
"projectType": "application",
"tags": ["brand:guestroom_city", "platform:mobile"],
"// targets": "to see all targets run: nx show project guestroom_city-mobile --web",
"targets": {}
}

"targets": {} means: “Use 100% plugin-inferred targets”


  1. Custom Build Process

    packages/design-tokens/project.json
    {
    "targets": {
    "build": {
    "executor": "nx:run-commands",
    "options": {
    "command": "style-dictionary build --config style-dictionary.config.js"
    }
    }
    }
    }
    • Style Dictionary, custom tooling, non-standard builds
    • Plugins can’t infer this - must be explicit
  2. Project Tagging/Organization

    {
    "tags": ["scope:shared", "type:tokens", "npm:public"]
    }
    • Tags for Nx graph filtering (nx affected --exclude=npm:private)
    • Division/brand organization
    • Scope restrictions
  3. Custom Executors

    {
    "targets": {
    "deploy": {
    "executor": "@cloudalt/deploy:firebase",
    "options": { "site": "production" }
    }
    }
    }
    • Custom Nx executors you’ve written
    • Special deployment targets
  4. Dependency Overrides

    {
    "implicitDependencies": ["config", "theme"],
    "targets": {
    "build": {
    "dependsOn": ["^build", "custom-prep"]
    }
    }
    }
    • Non-standard dependency graphs
    • Custom build order requirements
  1. Standard Expo/React Native App

    • @nx/expo/plugin handles everything
    • Automatically adds: start, build, prebuild, run-ios, run-android, etc.
  2. Standard Vite/React Web App

    • @nx/vite/plugin handles everything
    • Automatically adds: build, serve, test, preview
  3. Standard Library Package

    • Package.json detection is sufficient
    • TypeScript/ESLint plugins handle build/lint
  4. Configuration-Only Folder

    • packages/config/ with just tailwind-preset.js → No package.json or project.json needed
    • Pure config files don’t need Nx projects

  • 86 Nx-detected projects (from plugins + package.json detection)
  • 23 packages with package.json (auto-detected by Nx)
  • 16 packages with explicit project.json (most for tags/custom builds)
  • 60+ apps with minimal project.json (empty targets, 100% inferred)
packages/theme/
├── package.json # Nx auto-detects this
└── src/
└── index.ts
# NO project.json needed!
# Nx adds: build, lint, typecheck (from plugins)
apps/guestroom/guestroom_city/mobile/project.json
{
"name": "guestroom_city-mobile",
"tags": ["brand:guestroom_city", "platform:mobile"],
"targets": {} // Empty = 100% inferred from @nx/expo/plugin
}
packages/design-tokens/project.json
{
"name": "design-tokens",
"tags": ["scope:shared", "type:tokens"],
"targets": {
"build": {
"executor": "nx:run-commands",
"options": {
"command": "style-dictionary build --config style-dictionary.config.js"
}
}
}
}
// packages/some-library/project.json - DON'T DO THIS
{
"targets": {
"build": {
"executor": "@nx/js:tsc",
"options": { "outputPath": "dist/packages/some-library" }
},
"lint": { "executor": "@nx/eslint:lint" }
}
}
// ❌ These targets are already inferred by @nx/js and @nx/eslint plugins!
// ❌ Just delete this file and let plugins handle it

Symptom:

Terminal window
$ yarn nx build my-package
NX Cannot find configuration for 'my-package'

Diagnosis:

  1. Check if package.json exists: ls packages/my-package/package.json
  2. Check if Nx sees it: yarn nx show projects | grep my-package
  3. Check plugin configuration in nx.json

Solution:

  • If package.json exists but not detected → Check nx.json plugins
  • If no package.json → Add one with minimal { "name": "@cloudalt-frontend/my-package" }
  • If needs custom build → Add project.json with explicit targets

Symptom:

Terminal window
$ yarn nx build my-app
NX Cannot find target 'build' for project 'my-app'

Diagnosis:

  1. Check what targets exist: yarn nx show project my-app --web
  2. Check if plugin should provide this target
  3. Check if project.json has empty targets: {}

Solutions:

  • If plugin should provide it: Check plugin configuration in nx.json, ensure plugin is installed
  • If custom build needed: Add explicit target to project.json
  • If wrong target name: Use correct name (e.g., serve instead of start)

Symptom:

Terminal window
$ yarn nx build my-app
⚠️ Target "build" is defined multiple times

Cause: Both plugin inference AND explicit project.json define the same target

Solution:

// Remove explicit target from project.json
{
"targets": {
// "build": { ... } ← DELETE THIS
}
}

Let the plugin handle it!

Terminal window
# See what Nx knows about a project
yarn nx show project <project-name> --web
# Opens browser with full config:
# - All targets (explicit + inferred)
# - Dependencies
# - Source files
# - Plugin configuration

Terminal window
# List all Nx projects
yarn nx show projects
# Show specific project details
yarn nx show project design-tokens --web
# View dependency graph
yarn nx graph
# See affected projects
yarn nx affected:graph
Terminal window
# Find project.json files with explicit targets
find . -name "project.json" -exec grep -l '"targets": {' {} \; | \
while read file; do
echo "=== $file ==="
cat "$file" | grep -A 20 '"targets": {'
done
# Check if targets could be inferred instead

Migration Guide: Removing Unnecessary Config

Section titled “Migration Guide: Removing Unnecessary Config”
Terminal window
# Find all project.json files
find packages apps -name "project.json"

For each project.json:

  1. Has custom executors? → Keep it
  2. Has only standard targets (build/lint/test)? → Consider removing
  3. Has empty targets: {}? → Keep for tags/metadata only
  4. Has only tags? → Keep for organization
Terminal window
# Before removing, check what targets are inferred
yarn nx show project <name> --web
# Remove project.json
rm packages/some-lib/project.json
# Verify it still works
yarn nx show project some-lib --web
yarn nx build some-lib
yarn nx lint some-lib
Terminal window
# Test affected projects
yarn nx affected:build
yarn nx affected:test
yarn nx affected:lint
# Only commit if all pass
git add -A
git commit -m "refactor(nx): remove redundant project.json from <name>"

  • Plugins are maintained by Nx team
  • They handle common cases better than manual config
  • Only override when truly necessary
{
"tags": [
"scope:shared", // Shared across all apps
"type:ui", // UI component library
"division:stay-match", // Division-specific
"platform:web", // Web-only
"npm:public" // Published to npm
]
}
{
"targets": {
"build": {
"// NOTE": "Uses Style Dictionary instead of standard TypeScript build",
"executor": "nx:run-commands",
"options": {
"command": "style-dictionary build"
}
}
}
}
// ✅ Good: Just metadata
{
"name": "my-app",
"tags": ["platform:mobile", "brand:altfinder"],
"targets": {}
}
// ❌ Bad: Duplicating plugin inference
{
"name": "my-app",
"targets": {
"start": { "executor": "@nx/expo:start" },
"build": { "executor": "@nx/expo:build" }
// These are already inferred!
}
}
Terminal window
# Always test after config changes
yarn nx affected:build
yarn nx affected:test
yarn nx graph # Visual validation

{
"plugins": [
{
"plugin": "@nx/expo/plugin",
"options": {
"startTargetName": "start",
"buildTargetName": "build",
"runIosTargetName": "run-ios",
"runAndroidTargetName": "run-android"
}
},
{
"plugin": "@nx/vite/plugin",
"options": {
"buildTargetName": "build",
"testTargetName": "test",
"serveTargetName": "serve"
}
},
{
"plugin": "@nx/eslint/plugin",
"options": {
"targetName": "lint"
}
}
]
}
PluginDetectsInferred Targets
@nx/expo/pluginapp.json + Expo depsstart, build, prebuild, run-ios, run-android, install, export, submit
@nx/vite/pluginvite.config.*build, serve, test, preview, typecheck
@nx/react/router-pluginReact Router setupbuild, dev, start, typecheck
@nx/eslint/plugin.eslintrc.* or eslint.config.*lint
@nx/react-native/pluginReact Native setupstart, bundle, run-ios, run-android, pod-install

  • Validation script to detect over-configuration
  • Auto-fix script to remove redundant project.json files
  • CI check to prevent unnecessary explicit config
  • Documentation generator for project structure
  • Custom Nx plugin for brand-specific targets
  • Automated migration tool for config cleanup
  • Nx Cloud integration for distributed builds

To enforce this policy, we ship scripts that detect and fix redundant Nx configuration. These live in scripts/nx/.

  • scripts/nx/validate-inferred-tasks.mjs

    • Prints JSON with: enabled plugins, inferred target names, and a per-project analysis
    • Flags fully-redundant (all targets are inferred) and partially-redundant (mixed)
    • Exit code: 2 if any fully redundant projects are found
  • scripts/nx/auto-fix-inferred-tasks.mjs

    • Removes inferred-only targets from project.json while preserving custom targets, tags, and metadata
    • Safe: does not touch projects with custom executors/options/configurations/dependsOn
  • scripts/nx/projects-summary.mjs

    • Outputs a simple list of Nx projects via nx show projects for auditing and docs
Terminal window
# 1) Validate (report only)
node scripts/nx/validate-inferred-tasks.mjs | tee tmp/nx-validate.json
# 2) Auto-fix redundant inferred targets (safe)
node scripts/nx/auto-fix-inferred-tasks.mjs
# 3) Re-run validation (should be clean)
node scripts/nx/validate-inferred-tasks.mjs | tee tmp/nx-validate-after.json
# 4) Optional summary
node scripts/nx/projects-summary.mjs

Add a lightweight CI step that runs validation and fails on fully redundant configs. This prevents re-introducing explicit targets that plugins already infer.


packages/ui-stay-match/
├── package.json # Auto-detected by Nx
├── project.json # Optional: only for tags
└── src/
└── components/
// project.json (optional)
{
"name": "ui-stay-match",
"tags": ["scope:division", "division:stay-match", "type:ui"],
"targets": {} // Let plugins infer build/lint/test
}
apps/stay_match/staymatch_app/
├── mobile/
│ ├── package.json # Nx detects this
│ ├── project.json # Minimal: just tags
│ └── src/
└── shared/ # No package.json needed
└── assets/
packages/config/
├── tailwind-preset.js
└── metro.config.js
# NO package.json
# NO project.json
# Pure configuration files - not an Nx project

  • Does this have a package.json? (If yes, Nx auto-detects)
  • Is this a standard Expo/Vite/React app? (If yes, plugins handle it)
  • Do I need custom build steps? (If no, don’t add project.json)
  • Do I need special tags for filtering? (Only reason to add minimal project.json)
  • Have I checked yarn nx show projects? (Verify auto-detection works)
  • Document WHY in a comment
  • Use empty targets: {} if relying on inference
  • Add meaningful tags for organization
  • Test with yarn nx build <name>
  • Verify no conflicts: yarn nx show project <name> --web


Golden Rules:

  1. Let plugins do their job - they’re smarter than manual config
  2. Only add project.json for custom builds or tags
  3. Empty targets: {} means 100% inferred
  4. Package.json is enough for most libraries
  5. Test before committing - yarn nx affected:build

Remember: Over-configuration is the enemy of maintainability. When in doubt, let Nx infer!


Last Updated: October 12, 2025
Branch: feat/nx-integration
Status: Active Policy