惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cisco Blogs
Schneier on Security
Schneier on Security
T
Tor Project blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threat Research - Cisco Blogs
C
CERT Recently Published Vulnerability Notes
Security Latest
Security Latest
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
NISL@THU
NISL@THU
L
Lohrmann on Cybersecurity
Scott Helme
Scott Helme
Webroot Blog
Webroot Blog
Project Zero
Project Zero
Google Online Security Blog
Google Online Security Blog
The Last Watchdog
The Last Watchdog
Spread Privacy
Spread Privacy
Hacker News: Ask HN
Hacker News: Ask HN
PCI Perspectives
PCI Perspectives
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
W
WeLiveSecurity
Attack and Defense Labs
Attack and Defense Labs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News | PayPal Newsroom
Help Net Security
Help Net Security
The Hacker News
The Hacker News
H
Heimdal Security Blog
O
OpenAI News
S
Security @ Cisco Blogs
N
News and Events Feed by Topic
Cyberwarzone
Cyberwarzone
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - 叶小钗
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
I
Intezer
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security Affairs
P
Proofpoint News Feed
S
Secure Thoughts
腾讯CDC
Google DeepMind News
Google DeepMind News
量子位
罗磊的独立博客

Nx Blog

Sharing Tailwind CSS Styles Across Apps in a Monorepo | Nx Blog How SiriusXM Stays Competitive by Iterating and Getting to Market Fast | Nx Blog Agentic Experience Is the New Developer Experience | Nx Blog Nx Joins the Linux Foundation and the Agentic AI Foundation | Nx Blog A Monorepo Is NOT a Monolith | Nx Blog Why we deleted (most of) our MCP tools | Nx Blog Teach Your AI Agent How to Work in a Monorepo | Nx Blog How Broadcom stays efficient and nimble with monorepos | Nx Blog Why Monorepos are King in the Age of AI | Nx Blog Nx 2026 Roadmap: Expanding Agent Autonomy, Improving Performance, Better Polyglot and More | Nx Blog End to End Autonomous AI Agent Workflows with Nx | Nx Blog Autonomous Agents at Scale | Nx Blog Scaling 700+ Projects: How Nx Became a 'No-Brainer' for Caseware | Nx Blog Configure Tailwind v4 with Angular in an Nx Monorepo | Nx Blog The Missing Multiplier for AI Agent Productivity | Nx Blog A Year of Nx Webinars | Nx Blog Wrapping Up 2025 | Nx Blog Nx 22.3 Release: Angular 21 Support, tsgo Compiler, and Prettier v3 | Nx Blog Nx Cloud Release: Agent Resource Usage | Nx Blog Nx Platform Outperforms DIY Cache by 5x | Nx Blog An Nx Carol: Past, Present, and Future of Your Monorepo | Nx Blog Nx 22.1 Release: Terminal UI on Windows, Storybook 10, Vitest 4, and more! | Nx Blog The Compounding Effect: How Nx Features Multiply Performance Gains | Nx Blog 10 Monorepo Myths Debunked: Separating Fact from Fiction | Nx Blog Nx Cloud Release: Enterprise Task Analytics | Nx Blog Watch and Rebuild Storybook Dependencies with Nx | Nx Blog Book - React for Enterprise: Timeless Architecture for Enterprise Apps | Nx Blog Beyond Remote Cache: Unlock 70% More CI Performance | Nx Blog Nx 22 Release: Expanding the build platform | Nx Blog What's the Point of Generating All This Code If You Can't Merge It? | Nx Blog What's New in Nx Self-Healing CI | Nx Blog Nx Highlights: Smarter AI integration, all-new graph UI, and big new versions of your favorite tools | Nx Blog Making the Case for Smarter Monorepos, and How to Not Get Fooled by Myths | Nx Blog Integrating Biome in 20 Minutes | Nx Blog S1ngularity - What Happened, How We Responded, What We Learned | Nx Blog Stop Babysitting Your PRs: Self-Healing CI Cuts Time to Green by 50% | Nx Blog UKG Unifies Their Codebase and Eliminates CI Overhead to Focus on Customer Value | Nx Blog How Git Worktrees Changed My AI Agent Workflow | Nx Blog Nx Cloud Workspace Graph: See Your Organization's Code Structure Like Never Before | Nx Blog Seamless Java Deployment in Nx Using Docker | Nx Blog Getting Mobile Into Your Monorepo: Android + Nx | Nx Blog Polyglot Projects Made Easy: Integrating Spring Boot into an Nx Workspace | Nx Blog The Journey of the Nx Plugin for Gradle: From Prototype to Production | Nx Blog Combining Predictability and Intelligence With Nx Generators and AI | Nx Blog A New UI For The Humble Terminal | Nx Blog Continuous tasks are a huge DX improvement | Nx Blog New and Improved Module Federation Experience with Nx | Nx Blog A New UI for Nx Migration | Nx Blog Custom Task Runners and Self-Hosted Caching Changes | Nx Blog Enterprise Angular Monorepo Patterns | Nx Blog Using Rspack with Angular | Nx Blog Angular Architecture Guide To Building Maintainable Applications at Scale | Nx Blog Modern Angular Testing with Nx | Nx Blog Nx Update: 20.5 | Nx Blog Are Monorepos the Answer to Better AI-Assisted Development? | Nx Blog Making Cursor Smarter with an MCP Server For Nx Monorepos | Nx Blog React Development for 2025 | Nx Blog Using Apollo GraphQL in an Nx Workspace | Nx Blog Angular State Management for 2025 | Nx Blog Tailoring Nx for Your Organization | Nx Blog Nx Cloud Pipelines Come To Nx Console | Nx Blog Define the relationship with monorepos | Nx Blog See your affected project graph in Nx Cloud | Nx Blog Handling CORS In Your Workspace | Nx Blog Improve your architecture and CI pipeline times with Nx projects | Nx Blog Announcing Nx 20 | Nx Blog Introducing Nx Powerpack | Nx Blog Nx 19.5 is here! Stackblitz, Bun, Incremental Builds for Vite, Gradle Test Atomizer | Nx Blog Introducing Explain with AI | Nx Blog Nx Enterprise Podcast Episode 2: Tine Kondo | Nx Blog Monorepos and CI can be a Mess - Here's How Nx and Nx Cloud Fixed It | Nx Blog Nx Enterprise Podcast Episode 1: Hicham El Hammouchi | Nx Blog Nx 19.0 Release!! | Nx Blog Manage Your Gradle Project using Nx | Nx Blog Making the Argument for Monorepos | Nx Blog Reliable CI. A new execution model fixing both flakiness and slowness | Nx Blog Monorepos - Why Speed Matters | Nx Blog Nx Agents Walkthrough: Effortlessly Fast CI Built for Monorepos | Nx Blog Launch Nx Week Recap | Nx Blog Versioning and Releasing Packages in a Monorepo | Nx Blog Fast, Effortless CI | Nx Blog Introducing @nx/nuxt Enhanced Nuxt.js Support in Nx | Nx Blog What if Nx Plugins Were More Like VSCode Extensions | Nx Blog Monorepos: the Benefits, Challenges, and Importance of Tooling Support | Nx Blog Nx — Highlights of 2023 | Nx Blog Nx 17.2 Update | Nx Blog Unit Testing Expo Apps With Jest | Nx Blog Nx Docs AI Assistant | Nx Blog State Management Nx React Native/Expo Apps with TanStack Query and Redux | Nx Blog Nx 17 has Landed | Nx Blog Nx Conf 2023 — Recap | Nx Blog Nx Raises $16M Series A | Nx Blog Introducing Playwright Support for Nx | Nx Blog Nx 16.8 Release!!! | Nx Blog Step-by-Step Guide to Creating an Expo Monorepo with Nx | Nx Blog Qwikify your Development with Nx | Nx Blog Create Your Own create-react-app CLI | Nx Blog Storybook Interaction Tests in Nx | Nx Blog Evergreen Tooling — More than Just CodeMods | Nx Blog A Practical Guide on Effective AI Use - AI as Your Peer Programmer | Nx Blog
Avoiding Port Conflicts with Multiple Storybook Instances | Nx Blog
Nicolas Beaussart · 2024-12-19 · via Nx Blog

Nx Champion takeover

This post is written by our Nx Champion Nicolas Beaussart. Nicolas is an experienced Staff Engineer at PayFit and believer in open source. He is passionate about improving the DX on large monorepo thought architecture and tooling to empower others to shine brighter. With his experience spanning from DevOps, to backend and frontend, he likes to share his knowledge through teaching at his local university and online. In his free time, when he's not running some experiments, he's probably playing board games, tweaking his home server, or looking over his gemstone collection. You can find him on X/Twitter, Bluesky, and GitHub.

Ever tried juggling multiple Storybook instances in a monorepo, only to face port conflicts? It's like trying to fit several square pegs into the same round hole. But what if I told you there's a way to give each project its own unique port, automatically? Enter Nx's task inference feature – the beacon of hope for our monorepo Storybook aspirations.

Want to skip to the code?

Jump to the code

The problem

Consider the following setup:

packages/buttons/package.json

{
  "name": "@design-system/buttons",
  ...
  "scripts": {
    ...
    "storybook": "storybook dev",
    "build-storybook": "storybook build",
    "test-storybook": "start-server-and-test 'storybook dev --port 3000 --no-open' http://localhost:3000 'test-storybook --index-json --url=http://localhost:3000'"
  },
  ...
}

For each package in your monorepo, you have a test-storybook script that runs the Storybook test runner for that specific package. Now if you want to run them all in parallel (which you definitely should on CI), you will quickly run into port conflicts:

To fix it, you can manually assign different ports to each package. But not only is this annoying but it also won't scale.

The power of createNodes

The createNodes feature in Nx is a game-changer for creating inferences on projects. Today, we're diving into how we can leverage this to create dynamic Storybook targets with unique ports across our entire monorepo.

Why is this important? Well, imagine running multiple dev servers, test environments, and Storybook instances without worrying about port clashes. It's not just convenient – it's a productivity booster!

Creating a workspace inference plugin

To make this magic happen, we need to create a workspace plugin. Here's how: first, we create a new file for your plugin (eg tools/storybook.ts). In this file, we will define the base of our inference:

import { CreateNodesV2 } from '@nx/devkit';

export const createNodesV2: CreateNodesV2 = [
  '**/.storybook/main.{js,ts,mjs,mts,cjs,cts}',
  async (configFiles, options, context) => {
    return [];
  },
];

Here, we can see the createNodesV2 is an array, the first element being the entry point for our inference. In this case, we're looking for files with the .storybook/main.{js,ts,mjs,mts,cjs,cts} pattern as we want to capture all the Storybook configurations in our monorepo.

The second element is a function that will be called with the matching files. configFiles is an array of all the files found that matches the glob. This is where we can get creative with our dynamic configuration.

Finally, to be able to use it, you need to update your nx.json file to include the plugin:

{
  "plugins": ["./tools/storybook"]
}

To see whether you plugin loaded properly you can go to .nx/workspace-data/d/daemon.log and search for your plugin name. Behind the scenes the Nx Daemon re-calculates the project graph and loads all plugins, including ours.

TypeScript configuration

Make sure you have some tsconfig.json file in the monorepo root. Nx loads the plugin dynamically (without you having to precompile it) which requires some TypeScript context to be present. Have a look at the repo setup.

Dynamic projects creation

Now comes the fun part – dynamically creating project.json configurations. A static configuration of Storybook for your project might look as follows:

packages/somelib/project.json

{
  "targets": {
    "storybook": {
      "command": "storybook dev --port 3000",
      ...
    }
  }
}

We want to make the --port 3000 part dynamic, so we can run multiple Storybook instances in parallel.

Here's the gist of what we're doing:

  • Loop over the config files
  • Create one project per config file found

To do this, we will extract code from the Nx codebase to add our dynamic index to our function:

import {
  AggregateCreateNodesError,
  CreateNodesContextV2,
  CreateNodesResult,
  CreateNodesV2,
} from '@nx/devkit';

const processFile = (
  file: string,
  context: CreateNodesContextV2,
  port: number
) => {
  // TODO
  return {};
};

export const createNodesV2: CreateNodesV2 = [
  '**/.storybook/main.{js,ts,mjs,mts,cjs,cts}',
  async (configFiles, options, context) => {
    // Extracted from <https://github.com/nrwl/nx/blob/master/packages/nx/src/project-graph/plugins/utils.ts#L7>
    const results: Array<[file: string, value: CreateNodesResult]> = [];
    const errors: Array<[file: string, error: Error]> = [];
    await Promise.all(
      // iterate over the config files
      configFiles.map(async (file, index) => {
        try {
          // create a dynamic port for each file
          const value = processFile(file, context, 3000 + index);
          if (value) {
            results.push([file, value] as const);
          }
        } catch (e) {
          errors.push([file, e as Error] as const);
        }
      })
    );
    if (errors.length > 0) {
      throw new AggregateCreateNodesError(errors, results);
    }
    return results;
  },
];

If you look closely, you will see that we construct our port based on the index of the file. This is where we can generate unique ports for each project.

configFiles.map(async (file, index) => {
  try {
    // create a dynamic port for each file
    const value = processFile(file, context, 3000 + index);
    ...
  } catch (e) {
    errors.push([file, e as Error] as const);
  }
})

We're using the index to generate unique ports. Project 1 gets port 3000, project 2 gets 3001, and so on. It's simple, but effective.

Now, we can process our files to actually create targets:

import { CreateNodesContextV2 } from '@nx/devkit';
import { dirname } from 'node:path';

const processFile = (
  file: string,
  context: CreateNodesContextV2,
  port: number
) => {
  // We want to get the root of the project, this is how Nx know what project to merge this to
  let projectRoot = '';
  if (file.includes('/.storybook')) {
    projectRoot = dirname(file).replace('/.storybook', '');
  } else {
    projectRoot = dirname(file).replace('.storybook', '');
  }

  return {
    projects: {
      [projectRoot]: {
        // This is how Nx recognizes the project
        root: projectRoot,
        targets: {
          storybook: {
            command: `storybook dev --port ${port}`,
            options: { cwd: projectRoot },
          },
          'test-storybook': {
            // --index-json option is used as a workaround to avoid storybook test runner to check snapshot outside the project root: <https://github.com/storybookjs/test-runner/issues/415#issuecomment-1868117261>
            command: `start-server-and-test 'storybook dev --port ${port} --no-open' <http://localhost>:${port} 'test-storybook --index-json --url=http://localhost:${port}'`,
            options: { cwd: projectRoot },
          },
        },
      },
    },
  };
};

Reaping the benefits

With this setup, we can now:

  • Run concurrent Storybook instances without conflicts
  • Have consistent ports within each project
  • Easily spin up dev servers and test environments on matching ports

And the best part? It just works. Running a graph inspection on your projects will show each one with its unique port, ready for action.

Do you want to see it in action? Check out the repo, and run the following commands:

npm install
npm run nx run-many -t test-storybook

And it will run all the tests in all the projects, with the matching ports, without any conflicts!

Looking ahead: The infinite task proposal

While our current setup is pretty slick, the future looks even brighter. In our example, we had to rely on start-server-and-test package, but in the future, we will be able to rely on Nx infinite task proposal that is in the works that could make our concurrent configuration even smoother. Keep an eye on that – it's going to be a game-changer!

The create nodes API: A world of possibilities

What we've explored today is just the tip of the iceberg. The create nodes API opens up a world of possibilities for dynamic project configuration in your monorepo. Imagine having no static targets at all, with everything inferred based on your project structure.

While there are official Nx plugins available, don't be afraid to create your own. The power is in your hands to tailor your monorepo setup to your specific needs.

In the end, what we've achieved here is more than just unique ports – it's about creating a flexible, scalable infrastructure for your projects. So go ahead, give it a try, and watch your monorepo workflow transform. 🚀

Learn More

Also make sure to check out: