Skip to content

Reducing Configuration Boilerplate

Tutorial 7/8: Reduce configuration with plugins

Help me reduce configuration boilerplate in my Nx workspace.

Use my existing workspace and projects for hands-on examples.

First, consolidate shared task config into in . Then show me how to use Nx plugins to automatically infer tasks from my tooling (Vite, Jest, ESLint, etc.) so I don't need to configure each project manually.

Run to see where each task's configuration comes from.

Stay on-topic: only teach what's covered on this page. Do not introduce concepts from later tutorials.

Tutorial: https://canary.nx.dev/docs/getting-started/tutorials/reducing-configuration-boilerplate.md

Help me reduce configuration boilerplate in my Nx workspace.

Use my existing workspace and projects for hands-on examples.

First, consolidate shared task config into in . Then show me how to use Nx plugins to automatically infer tasks from my tooling (Vite, Jest, ESLint, etc.) so I don't need to configure each project manually.

Run to see where each task's configuration comes from.

Stay on-topic: only teach what's covered on this page. Do not introduce concepts from later tutorials.

Tutorial: https://canary.nx.dev/docs/getting-started/tutorials/reducing-configuration-boilerplate.md

Manually configuring tasks, caching, inputs, and outputs for every project works, but it doesn't scale to dozens or hundreds of projects. Nx provides two mechanisms to reduce this boilerplate: targetDefaults for shared configuration and plugins for automatic task inference.

The examples below use Vite and Vitest, but the concepts apply to any tool. Substitute your own build and test commands as needed.

Consider a workspace with 20 libraries, each with verbose task configuration:

packages/my-lib/package.json
{
"scripts": {
"build": "vite build",
"test": "vitest run",
},
"nx": {
"targets": {
"build": {
"cache": true,
"dependsOn": ["^build"],
"inputs": [
"{projectRoot}/src/**/*",
"{projectRoot}/vite.config.ts",
"{projectRoot}/tsconfig.json",
],
"outputs": ["{projectRoot}/dist"],
},
"test": {
"cache": true,
"inputs": ["{projectRoot}/src/**/*", "{projectRoot}/vitest.config.ts"],
"outputs": ["{projectRoot}/coverage"],
},
},
},
}

That's a lot of repetition across 20 projects. And every time you change the Vite output directory, you'd need to update all of them.

Move shared configuration to nx.json so projects inherit defaults:

nx.json
{
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": ["^build"],
},
"test": {
"cache": true,
},
},
}

Now each project only needs to specify what's unique:

packages/my-lib/package.json
{
"scripts": {
"build": "vite build",
"test": "vitest run",
},
"nx": {
"targets": {
"build": {
"inputs": ["{projectRoot}/src/**/*", "{projectRoot}/vite.config.ts"],
"outputs": ["{projectRoot}/dist"],
},
"test": {
"inputs": ["{projectRoot}/src/**/*", "{projectRoot}/vitest.config.ts"],
"outputs": ["{projectRoot}/coverage"],
},
},
},
}

Better, but you still repeat inputs and outputs across projects with the same tooling.

Nx plugins can read your existing tooling configuration files, like vite.config.ts, jest.config.ts, or eslint.config.mjs, and automatically create tasks with the correct caching settings. No project.json needed.

Try adding a plugin to your workspace:

Terminal window
nx add @nx/vite

This installs @nx/vite and registers it in nx.json. Some plugins may also need nx sync to update workspace configuration files (e.g., TypeScript project references). See maintain TypeScript monorepos for details.

After installing, check what tasks were inferred for one of your projects:

Terminal window
nx show project my-app

You should see tasks like build, test, and serve that were automatically created from your vite.config.ts file.

The plugin reads your Vite configuration and sets up correct caching, inputs, and outputs without any manual configuration.

The plugin is registered in nx.json:

nx.json
{
"plugins": [
{
"plugin": "@nx/vite/plugin",
"options": {
"buildTargetName": "build",
"testTargetName": "test",
"serveTargetName": "serve",
},
},
],
}

Now, any project with a vite.config.ts automatically gets build, test, and serve tasks, with correct inputs, outputs, and caching, without any project.json configuration.

Use nx show project to see all tasks for a project, including where they come from:

Terminal window
nx show project my-lib
Project details for my-lib

my-lib

React
Vite
ESLint

Root: packages/my-lib

Type:library

Targets

  • build

    Vite

    vite build

    Cacheable
  • lint

    ESLint

    eslint .

    Cacheable
  • test

    Vite

    vitest run

    Cacheable

The project details view shows each task's source (plugin, targetDefaults, or project.json) and its computed settings.

Task configuration can come from three sources, applied in this order:

  1. Plugin-inferred: automatic from tooling config (lowest priority)
  2. targetDefaults: shared defaults in nx.json
  3. Project-level: explicit project.json or package.json config (highest priority)

Each layer can override the previous one. This means you can use plugins for sensible defaults and only add project-level config when a project needs something different.

Plugins are optional. The explicit task configuration from Configuring Tasks works perfectly well. Use plugins when:

  • You have many projects with the same tooling (Vite, Jest, ESLint, etc.)
  • You want caching configured automatically with correct inputs/outputs
  • You prefer minimal configuration files

Stick with explicit configuration when:

  • You have unique build setups that plugins don't cover
  • You want full control over every task detail
  • Your team prefers explicit over implicit