structural-engineering-and-design
Creating Custom Templates and Standards in Nx for Consistency
Table of Contents
Every engineering team that grows beyond a handful of projects eventually confronts the same problem: how do you keep dozens, or hundreds, of codebases consistent? Without explicit standards, each new project becomes a snowflake – different folder structures, different dependency versions, different lint rules, different testing setups. The result is increased cognitive load, slower onboarding, and a maintenance nightmare. Nx, the monorepo build framework from Nrwl, was designed to solve exactly this problem. By providing a generator system, shared configuration files, and powerful enforcement mechanisms, Nx gives teams the tools to bake consistency directly into their workflow. This article walks through practical strategies for creating custom templates and standards in Nx, from designing generators to enforcing rules across every project in your workspace.
The Challenge of Consistency at Scale
In a typical developer workflow, consistency is achieved through documentation and code review. A team writes a wiki page describing the preferred project structure, the naming conventions for libraries, and the required dependencies. New projects are started by copying an old project and renaming files. This manual process is fragile: someone skips a step, uses an outdated version of a configuration file, or interprets the guidelines differently. The result is drift. Over time, the team spends more time debugging differences between projects than building features.
Nx replaces this ad-hoc approach with a programmatic one. Instead of copying and pasting, developers run a command – nx g @myorg/my-plugin:library my‑lib – and receive a project that conforms to every standard the team has defined. The template is not a static copy; it's a generator that can embed logic, ask for user input, and wire up configuration automatically. This eliminates the variability that creeps in with manual setup. Consistency becomes the path of least resistance.
How Nx Enables Consistency
Nx achieves consistency through three core mechanisms: generators, shared configurations, and execution gates. Generators (often called "schematics" in older Nx documentation) are functions that create or modify files. They are the primary tool for custom templates. Shared configurations like nx.json, .eslintrc.json at the root, and tsconfig.base.json propagate settings across all projects. Execution gates – such as linting and testing steps in your CI pipeline – prevent non-compliant code from ever merging.
Generators: The Foundation of Custom Templates
An Nx generator is a TypeScript (or JavaScript) file that exports a generator function. This function receives a Tree (an abstraction over the file system) and a Schema (the options passed by the developer). Inside the generator, you can read, create, update, or delete files. Nx ships with a set of built-in generators for Angular, React, Node, and other frameworks, but you can create your own to codify any pattern your team uses.
For example, imagine your team requires every front-end library to include a specific folder structure: a src/lib directory for barrel exports, a src/components folder for React components, and a __tests__ folder for unit tests. A custom generator can scaffold this automatically. It can also add the library to a global barrel file, register it in tsconfig.paths, and configure any necessary ESLint overrides. The developer only provides the library name and an optional directory; the generator handles the rest.
Designing Custom Generators in Nx
Creating a custom generator involves four high-level steps: defining the generator interface, implementing the logic, registering it in workspace.json or project.json, and testing it against a mock tree. Below I expand each step with practical advice.
Step 1: Define the Schema and Interface
Start by deciding what parameters the generator will accept. Common options include name, directory, tags (for Nx dependency constraints), and style (CSS, SCSS, CSS‑in‑JS). These are defined in a schema.json file, which also specifies validation rules (e.g., required fields, default values). The Nx CLI uses this schema to prompt users or validate input. A well-designed schema reduces confusion and prevents malformed projects from being created.
Step 2: Implement the Generator Logic
The generator function receives a Tree and the validated Schema. Use the @nrwl/devkit utilities to interact with the tree. For example:
- generateFiles – copies template files from a folder, substituting variables with schema values.
- addProjectConfiguration – registers the new project in the workspace.
- updateJson – modifies
tsconfig,jest.config, orproject.json. - addDependenciesToPackageJson – ensures required packages are installed.
Template files are stored alongside the generator and use EJS syntax for variable interpolation. For instance, a README.md template might contain <%= name %> to be replaced with the library name. You can also include conditional blocks or loops inside templates if the logic is simple; for more complex logic, prefer to manipulate the tree in the generator itself.
Step 3: Register the Generator
Generators are registered in the project.json (or workspace.json for older setups) under the generators section. For a local plugin, the generators.json file within the plugin's project specifies which generators are available and where their code lives. Once registered, the generator becomes visible to nx generate and can be discovered by other developers through the CLI or Nx Console.
Step 4: Test the Generator
Nx provides a testing helper, createTreeWithEmptyWorkspace, from @nrwl/devkit/testing. Write unit tests that invoke your generator on a virtual tree and assert the resulting file structure. Also test edge cases: what happens if the library already exists? If the directory is nested? If required options are missing? Robust testing ensures the generator remains reliable as the workspace evolves.
Enforcing Standards with Nx
Creating templates is only half the picture. Even with perfect generators, a developer can still modify files after generation in ways that break standards. Nx makes it possible to enforce standards automatically, without relying on human code review for every file.
Shared Linting and Formatting Configurations
Place ESLint and Prettier configuration files at the workspace root. Nx's ESLint plugin (@nrwl/eslint-plugin-nx) allows you to define workspace-wide rules while still allowing project-level overrides. For example, you can enforce that all libraries must follow a naming convention (e.g., prefix feature-, data-access-, ui-) by writing a custom ESLint rule or using @nrwl/nx/enforce-module-boundaries. The enforce-module-boundaries rule is particularly powerful: it uses the tags you assigned to projects during generation to prohibit certain dependency relationships. A data-access library, for instance, cannot import from a UI library. This architectural constraint is enforced at lint time, not just in design documents.
CI Integration with Nx Affected Commands
Nx's affected commands (nx affected:lint, nx affected:test, nx affected:build) run only on projects that have changed, making fast feedback possible even in large monorepos. Configure your CI pipeline to run nx affected:lint on every PR. If a project fails linting, the PR cannot be merged. This catch unfailing adherence to your custom linting rules. Combine with nx format:check to enforce Prettier formatting. Together, these automated checks make deviation from standards visible and blockable.
Shared Dependency Versions
Nx resolves dependencies via a single package.json at the root. This means all projects share the same version of, say, React or Lodash – eliminating version skew. For monorepos with different frameworks, you can still use workspace-level dependency management through pnpm or yarn workspaces, but Nx enforces that no project sneaks in a conflicting version via its own package.json. You can also create custom generator schemas that pin specific dependency versions and verify them with automated audits.
Code Generation as a Gate
One often overlooked enforcement technique is requiring that new projects be created only through generators. In some Nx workspaces, you can publish a plugin that provides the only allowed way to create a library or application. If a developer manually creates files, they risk breaking the dependency graph and causing nx affected to behave incorrectly. By making the generator the sole supported path, you institutionalize the templates and standards.
Beyond Scaffolding: Standardizing Architecture
Custom templates and lint rules can enforce not just file structure and code style but also architectural patterns. This is where Nx shines compared to simpler scaffolding tools.
Folder Structure as a Contract
Decide on a standard hierarchy for your workspace. A common pattern for front-end monorepos is:
- apps/ – deployable applications (web, mobile, serverless functions)
- libs/ – shared libraries, further grouped by domain or layer:
- libs/shared/ui – reusable presentational components
- libs/shared/utils – pure utility functions
- libs/feature/dashboard – dashboards feature logic
- libs/feature/settings – settings feature logic
Your custom generator can enforce this by defaulting to the libs directory, prompting for a category (e.g., feature, shared, data-access), and placing the project accordingly. Over time, every developer internalises the structure because it's the only structure the generator creates.
Naming Conventions and Tags
Nx tags are metadata attached to projects that the module boundary rule uses. For example, a library tagged type:feature might be allowed to import type:ui but not type:data-access. Define your tagging convention in a workspace-level document, and implement it in the generator: when a developer creates a new library of type "data-access," the generator automatically adds the type:data-access tag. Then the lint rule handles the rest.
Shared Boilerplate for Common Patterns
Consider building generators for cross-cutting concerns: logging middleware, error boundaries, API client stubs, route definitions. Instead of each developer implementing a logging utility differently, a generator creates a consistent logging service with the team's chosen library (e.g., Winston, Pino) pre-configured. The same principle applies to GraphQL queries, REST endpoints, state management slices, and more. Every generated project becomes a model of the team's best practices.
Integrating Standards into Your Team Workflow
Technical solutions are only effective if the team adopts them. Here are practical steps to roll out custom templates and standards without overwhelming your developers.
Start Small: One Generator, One Rule
Don't try to generate every possible project type on day one. Identify the most common project type your team creates – likely a library for a specific layer or a new application shell – and build a generator for that. Simultaneously, introduce one enforcement rule, such as Prettier formatting in CI. Let the team experience the benefit before adding more complexity.
Document Your Generators and Conventions
Generators are useless if no one knows they exist. Add a docs/ directory to the workspace root with a short page listing all custom generators, their options, and examples. Include the naming conventions and tag definitions. Keep this document updated as the workspace evolves. Better yet, link to it from the output of the nx list command or from error messages in the module boundary rule.
Use Nx Console for Discoverability
Nx Console is a VS Code and JetBrains plugin that provides a GUI for running generators. It automatically lists all generators from installed plugins, including your custom ones. Encourage your team to use it – they can see exactly what a generator creates before running it, and the schema prompts make options clear. This lowers the barrier to adopting templates.
Iterate Based on Feedback
No generator is perfect forever. After a few weeks, gather feedback from developers: what did the generator miss? What configuration did they have to modify manually after generation? Address these pain points by updating the generator. Treat generators as living code that evolves alongside the team's practices. Nx makes it easy to update them because the generator logic is version‑controlled and tested.
Measuring the Impact of Consistency
How do you know if your custom templates and standards are working? Look for these leading indicators:
- Reduction in bootstrap time: How long does it take a new team member to set up a local development environment and create their first feature? When generators handle setup, this drops from hours to minutes.
- Decrease in manual configuration changes: Check git history for commits that adjust tsconfig paths, add missing dependencies, or rename folders after initial creation. Fewer such commits indicate that the generator is delivering complete scaffolding.
- Fewer lint warnings in code review: If your CI gates are working, developers see lint errors before they push. Over time, the number of lint‑related comments in pull requests should decrease.
- Faster PR review cycles: When every project looks alike, reviewers can focus on logic and business decisions instead of arguing about folder arrangements or naming.
Track these metrics informally with your team. If you use a tool like Code Climate or a dashboard for CI latency, you can derive objective numbers. The goal is not to reach perfection, but to continuously reduce the friction caused by inconsistency.
Conclusion
Consistency in a monorepo is not an accident; it is the result of deliberate tooling and team discipline. Nx gives you the power to encode your team's architectural decisions into generators, enforce them with linting and CI gates, and evolve them as your organization learns. Custom templates eliminate the manual, error‑prone step of copying and pasting old projects. Standards enforced by enforce-module-boundaries and shared configuration prevent drift over time. The investment in building these generators pays for itself quickly as your workspace grows from tens of projects to hundreds. Start with one generator, one rule, and one CI step. From there, your team will discover their own patterns to standardize – and Nx will be ready to handle them.