Nx Integration Guide - DRY Configuration Approach
Date: October 12, 2025
Status: ✅ Active Policy
Branch: feat/nx-integration
Overview
Section titled “Overview”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.
Why This Matters
Section titled “Why This Matters”- 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
How Nx Auto-Detection Works
Section titled “How Nx Auto-Detection Works”1. Package.json Detection
Section titled “1. Package.json Detection”Nx automatically detects any directory with a package.json file:
packages/theme/package.json → Nx detects as "theme" projectpackages/hooks/package.json → Nx detects as "hooks" projectapps/stay_match/*/mobile/package.json → Nx detects as mobile appsNo project.json needed! Nx knows these exist.
2. Plugin-Based Inference
Section titled “2. Plugin-Based Inference”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.
3. Empty project.json = 100% Inferred
Section titled “3. Empty project.json = 100% Inferred”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”
When to Add project.json
Section titled “When to Add project.json”✅ ONLY Add project.json When:
Section titled “✅ ONLY Add project.json When:”-
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
-
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
- Tags for Nx graph filtering (
-
Custom Executors
{"targets": {"deploy": {"executor": "@cloudalt/deploy:firebase","options": { "site": "production" }}}}- Custom Nx executors you’ve written
- Special deployment targets
-
Dependency Overrides
{"implicitDependencies": ["config", "theme"],"targets": {"build": {"dependsOn": ["^build", "custom-prep"]}}}- Non-standard dependency graphs
- Custom build order requirements
❌ DO NOT Add project.json When:
Section titled “❌ DO NOT Add project.json When:”-
Standard Expo/React Native App
@nx/expo/pluginhandles everything- Automatically adds: start, build, prebuild, run-ios, run-android, etc.
-
Standard Vite/React Web App
@nx/vite/pluginhandles everything- Automatically adds: build, serve, test, preview
-
Standard Library Package
- Package.json detection is sufficient
- TypeScript/ESLint plugins handle build/lint
-
Configuration-Only Folder
packages/config/with justtailwind-preset.js→ No package.json or project.json needed- Pure config files don’t need Nx projects
Current Monorepo Status
Section titled “Current Monorepo Status”Summary
Section titled “Summary”- 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)
Examples
Section titled “Examples”✅ Good: Auto-Detected Library
Section titled “✅ Good: Auto-Detected Library”packages/theme/├── package.json # Nx auto-detects this└── src/ └── index.ts
# NO project.json needed!# Nx adds: build, lint, typecheck (from plugins)✅ Good: Minimal App Config
Section titled “✅ Good: Minimal App Config”{ "name": "guestroom_city-mobile", "tags": ["brand:guestroom_city", "platform:mobile"], "targets": {} // Empty = 100% inferred from @nx/expo/plugin}✅ Good: Custom Build Process
Section titled “✅ Good: Custom Build Process”{ "name": "design-tokens", "tags": ["scope:shared", "type:tokens"], "targets": { "build": { "executor": "nx:run-commands", "options": { "command": "style-dictionary build --config style-dictionary.config.js" } } }}❌ Bad: Unnecessary Explicit Config
Section titled “❌ Bad: Unnecessary Explicit Config”// 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 itTroubleshooting
Section titled “Troubleshooting”Error: “Cannot find project”
Section titled “Error: “Cannot find project””Symptom:
$ yarn nx build my-packageNX Cannot find configuration for 'my-package'Diagnosis:
- Check if package.json exists:
ls packages/my-package/package.json - Check if Nx sees it:
yarn nx show projects | grep my-package - Check plugin configuration in
nx.json
Solution:
- If package.json exists but not detected → Check
nx.jsonplugins - If no package.json → Add one with minimal
{ "name": "@cloudalt-frontend/my-package" } - If needs custom build → Add
project.jsonwith explicit targets
Error: “Target does not exist”
Section titled “Error: “Target does not exist””Symptom:
$ yarn nx build my-appNX Cannot find target 'build' for project 'my-app'Diagnosis:
- Check what targets exist:
yarn nx show project my-app --web - Check if plugin should provide this target
- Check if
project.jsonhas emptytargets: {}
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.,
serveinstead ofstart)
Error: Target conflicts/duplicates
Section titled “Error: Target conflicts/duplicates”Symptom:
$ yarn nx build my-app⚠️ Target "build" is defined multiple timesCause: 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!
Debugging: See All Targets
Section titled “Debugging: See All Targets”# See what Nx knows about a projectyarn nx show project <project-name> --web
# Opens browser with full config:# - All targets (explicit + inferred)# - Dependencies# - Source files# - Plugin configurationValidation Commands
Section titled “Validation Commands”Check Current Configuration
Section titled “Check Current Configuration”# List all Nx projectsyarn nx show projects
# Show specific project detailsyarn nx show project design-tokens --web
# View dependency graphyarn nx graph
# See affected projectsyarn nx affected:graphAudit for Over-Configuration
Section titled “Audit for Over-Configuration”# Find project.json files with explicit targetsfind . -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 insteadMigration Guide: Removing Unnecessary Config
Section titled “Migration Guide: Removing Unnecessary Config”Step 1: Identify Redundant project.json
Section titled “Step 1: Identify Redundant project.json”# Find all project.json filesfind packages apps -name "project.json"Step 2: Check Each One
Section titled “Step 2: Check Each One”For each project.json:
- Has custom executors? → Keep it
- Has only standard targets (build/lint/test)? → Consider removing
- Has empty
targets: {}? → Keep for tags/metadata only - Has only tags? → Keep for organization
Step 3: Test Removal
Section titled “Step 3: Test Removal”# Before removing, check what targets are inferredyarn nx show project <name> --web
# Remove project.jsonrm packages/some-lib/project.json
# Verify it still worksyarn nx show project some-lib --webyarn nx build some-libyarn nx lint some-libStep 4: Commit Carefully
Section titled “Step 4: Commit Carefully”# Test affected projectsyarn nx affected:buildyarn nx affected:testyarn nx affected:lint
# Only commit if all passgit add -Agit commit -m "refactor(nx): remove redundant project.json from <name>"Best Practices
Section titled “Best Practices”1. Trust the Plugins
Section titled “1. Trust the Plugins”- Plugins are maintained by Nx team
- They handle common cases better than manual config
- Only override when truly necessary
2. Use Tags Liberally
Section titled “2. Use Tags Liberally”{ "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 ]}3. Document Custom Targets
Section titled “3. Document Custom Targets”{ "targets": { "build": { "// NOTE": "Uses Style Dictionary instead of standard TypeScript build", "executor": "nx:run-commands", "options": { "command": "style-dictionary build" } } }}4. Keep Minimal Apps Config
Section titled “4. Keep Minimal Apps Config”// ✅ 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! }}5. Validate Before Committing
Section titled “5. Validate Before Committing”# Always test after config changesyarn nx affected:buildyarn nx affected:testyarn nx graph # Visual validationPlugin Configuration Reference
Section titled “Plugin Configuration Reference”Current Plugins (nx.json)
Section titled “Current Plugins (nx.json)”{ "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" } } ]}What Each Plugin Provides
Section titled “What Each Plugin Provides”| Plugin | Detects | Inferred Targets |
|---|---|---|
@nx/expo/plugin | app.json + Expo deps | start, build, prebuild, run-ios, run-android, install, export, submit |
@nx/vite/plugin | vite.config.* | build, serve, test, preview, typecheck |
@nx/react/router-plugin | React Router setup | build, dev, start, typecheck |
@nx/eslint/plugin | .eslintrc.* or eslint.config.* | lint |
@nx/react-native/plugin | React Native setup | start, bundle, run-ios, run-android, pod-install |
Future Enhancements
Section titled “Future Enhancements”Planned
Section titled “Planned”- 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
Under Consideration
Section titled “Under Consideration”- Custom Nx plugin for brand-specific targets
- Automated migration tool for config cleanup
- Nx Cloud integration for distributed builds
Script Automation
Section titled “Script Automation”To enforce this policy, we ship scripts that detect and fix redundant Nx configuration. These live in scripts/nx/.
Scripts
Section titled “Scripts”-
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) andpartially-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.jsonwhile preserving custom targets, tags, and metadata - Safe: does not touch projects with custom executors/options/configurations/dependsOn
- Removes inferred-only targets from
-
scripts/nx/projects-summary.mjs- Outputs a simple list of Nx projects via
nx show projectsfor auditing and docs
- Outputs a simple list of Nx projects via
How to run
Section titled “How to run”# 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 summarynode scripts/nx/projects-summary.mjsCI suggestion
Section titled “CI suggestion”Add a lightweight CI step that runs validation and fails on fully redundant configs. This prevents re-introducing explicit targets that plugins already infer.
Common Patterns
Section titled “Common Patterns”Pattern: Division-Specific UI Library
Section titled “Pattern: Division-Specific UI Library”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}Pattern: Mono-App with Shared Assets
Section titled “Pattern: Mono-App with Shared Assets”apps/stay_match/staymatch_app/├── mobile/│ ├── package.json # Nx detects this│ ├── project.json # Minimal: just tags│ └── src/└── shared/ # No package.json needed └── assets/Pattern: Shared Configuration Package
Section titled “Pattern: Shared Configuration Package”packages/config/├── tailwind-preset.js└── metro.config.js
# NO package.json# NO project.json# Pure configuration files - not an Nx projectChecklist: Adding New Projects
Section titled “Checklist: Adding New Projects”Before Creating project.json:
Section titled “Before Creating project.json:”- 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)
If Adding project.json:
Section titled “If Adding project.json:”- 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
Resources
Section titled “Resources”Summary
Section titled “Summary”Golden Rules:
- ✅ Let plugins do their job - they’re smarter than manual config
- ✅ Only add project.json for custom builds or tags
- ✅ Empty
targets: {}means 100% inferred - ✅ Package.json is enough for most libraries
- ✅ 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