<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dawid Nitka</title>
    <description>The latest articles on DEV Community by Dawid Nitka (@nagell).</description>
    <link>https://dev.to/nagell</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1230391%2Fb73c8f4a-9b91-489e-b826-150bf4f776fc.png</url>
      <title>DEV Community: Dawid Nitka</title>
      <link>https://dev.to/nagell</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nagell"/>
    <language>en</language>
    <item>
      <title>Save 60-90% of Your Claude Code Tokens With Two Tools</title>
      <dc:creator>Dawid Nitka</dc:creator>
      <pubDate>Tue, 16 Jun 2026 01:44:22 +0000</pubDate>
      <link>https://dev.to/nagell/save-60-90-of-your-claude-code-tokens-with-two-tools-3da3</link>
      <guid>https://dev.to/nagell/save-60-90-of-your-claude-code-tokens-with-two-tools-3da3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Two tools cut Claude Code token usage at two different layers. RTK is a shell proxy that compresses command output before it ever reaches the context window. context-mode is a Claude Code plugin that does heavy tool work in a sandbox and hands back only the answer. They stack cleanly on top of each other, and a single skill installs both. This article explains how each one works and how to wire them in.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Two commands into a session, my context window was already a third full, and I hadn't written a line of code yet. A &lt;code&gt;pnpm install&lt;/code&gt; had dumped its entire dependency tree, a &lt;code&gt;git log&lt;/code&gt; paid out two hundred commits, then a stack trace landed in full. None of that was work I'd asked for - it just sat there in the context window eating tokens on every turn.&lt;/p&gt;

&lt;p&gt;Most of the token budget goes on that boring output - the installs, the logs, the traces - which piles up and gets re-read on every single turn, never on the clever reasoning you actually wanted. Two tools attack that pile from two directions. Here's how they work, and how to install both in one command.&lt;/p&gt;

&lt;p&gt;This is the last article in the series, and it builds on the skill pattern from the third. You can pass this article URL straight to Claude Code and follow along.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the tokens actually go
&lt;/h2&gt;

&lt;p&gt;Picture the context window as a desk. Everything Claude needs stays on the desk so it can glance at it: your prompts, its replies and the output of every command it ran. The desk has a size limit, and once something is on it, it gets re-read on every turn until it falls off the edge.&lt;/p&gt;

&lt;p&gt;Two kinds of clutter land there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Command output&lt;/strong&gt; that arrives bloated. A dependency install, a long log, a verbose test run. It enters once and costs tokens on every turn after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The accumulated pile&lt;/strong&gt; itself. Even reasonably sized outputs add up across a long session until the desk is buried.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two tools map onto those two problems. RTK trims the output before it ever reaches the desk. context-mode keeps the heaviest work off the desk altogether.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 1: RTK, trim it before it lands
&lt;/h2&gt;

&lt;p&gt;RTK is a shell-level proxy. It sits between Claude and the commands it runs, intercepts the output and compresses it before it reaches the context window. The claim is 60 to 90 percent savings on typical dev operations, and it ships a &lt;code&gt;rtk gain&lt;/code&gt; command so you can check your real number instead of taking the claim on faith.&lt;/p&gt;

&lt;p&gt;The mechanism is a hook. After you set up the Claude Code integration, every Bash command Claude runs gets transparently rewritten to route through RTK. &lt;code&gt;git status&lt;/code&gt; becomes &lt;code&gt;rtk git status&lt;/code&gt; behind the scenes, with no change to how you or Claude write commands and no token overhead for the rewrite itself.&lt;/p&gt;

&lt;p&gt;Install is two moves: drop in the binary, then wire the integration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# after installing the binary (follow the README for your platform)&lt;/span&gt;
rtk init &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then confirm it's the right tool and it's working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rtk &lt;span class="nt"&gt;--version&lt;/span&gt;
rtk gain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One trap worth naming: there's a second, unrelated project that also ships a binary called &lt;code&gt;rtk&lt;/code&gt; (a Rust type toolkit). If &lt;code&gt;rtk gain&lt;/code&gt; comes back "command not found" after a clean install, you almost certainly have the other one. Check with &lt;code&gt;which rtk&lt;/code&gt; and grab the token-killer from its own repo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 2: context-mode, keep it off the desk
&lt;/h2&gt;

&lt;p&gt;context-mode comes at the problem from the other side. It's a Claude Code plugin, MCP server plus hooks, and instead of trimming output it relocates the heavy work.&lt;/p&gt;

&lt;p&gt;When Claude needs to process something large - a big log, or a sprawling JSON payload - context-mode runs that work in a sandbox and returns only the derived answer. The raw bytes never enter the context window. You asked how many errors are in a 10,000-line log, you get back "47" and the breakdown, not the log. The desk stays clear because the bulky part of the job happened in the magic drawer.&lt;/p&gt;

&lt;p&gt;Because it registers an MCP server and hooks, context-mode only activates after a full restart of Claude Code. Installing it is the usual two plugin commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin marketplace add mksglu/context-mode
/plugin install context-mode@context-mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart, then verify it came up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/context-mode:ctx-doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The shortcut: one command for both
&lt;/h2&gt;

&lt;p&gt;Installing two tools by hand, across platforms, with steps that drift as the projects evolve, is exactly the kind of chore Article 3 argued you should automate. So this is a skill too.&lt;/p&gt;

&lt;p&gt;The catch is that install steps go stale. Hardcode them today and the article rots the moment either project changes a flag. So the skill doesn't hardcode anything. It fetches the current README for each tool at run time and follows whatever the install section says right now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup-token-savings&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install RTK and context-mode to cut token usage&lt;/span&gt;
&lt;span class="na"&gt;disable-model-invocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## Instructions&lt;/span&gt;

&lt;span class="gu"&gt;### Step 1 - Fetch latest install instructions&lt;/span&gt;

Use WebFetch to read the current README for each tool, and follow its
Installation section to check the steps below:
&lt;span class="p"&gt;
-&lt;/span&gt; RTK: https://github.com/rtk-ai/rtk
&lt;span class="p"&gt;-&lt;/span&gt; context-mode: https://github.com/mksglu/context-mode

&lt;span class="gu"&gt;### Step 2 - Install RTK&lt;/span&gt;

Follow the RTK README for the current platform (detect with &lt;span class="sb"&gt;`uname -s`&lt;/span&gt; /
&lt;span class="sb"&gt;`uname -r`&lt;/span&gt;), then run &lt;span class="sb"&gt;`rtk init -g`&lt;/span&gt;. Verify with &lt;span class="sb"&gt;`rtk --version`&lt;/span&gt; and &lt;span class="sb"&gt;`rtk gain`&lt;/span&gt;.

&lt;span class="gu"&gt;### Step 3 - Install context-mode&lt;/span&gt;

Follow the context-mode README. Typically:
claude plugin marketplace add mksglu/context-mode
claude plugin install context-mode@context-mode

&lt;span class="gu"&gt;### Final step&lt;/span&gt;

Tell the user to fully restart Claude Code, since context-mode's MCP server and
hooks only activate on restart. Then suggest /context-mode:ctx-doctor to verify.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop that into &lt;code&gt;skills/setup-token-savings/SKILL.md&lt;/code&gt;, and a fresh machine gets both tools with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/your-plugin:setup-token-savings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A slightly more verbose version lives in the reference repo, in the base plugin's skills: &lt;a href="https://github.com/Nagell/claude-marketplace" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Check what you saved
&lt;/h2&gt;

&lt;p&gt;The point of &lt;code&gt;rtk gain&lt;/code&gt; is that you don't have to trust the headline number. It reports your actual savings from real usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rtk gain            &lt;span class="c"&gt;# total savings so far&lt;/span&gt;
rtk gain &lt;span class="nt"&gt;--history&lt;/span&gt;  &lt;span class="c"&gt;# per-command breakdown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  My result after some time
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;RTK Token Savings (Global Scope)
════════════════════════════════════════════════════════════
Total commands:    1539
Input tokens:      1.3M
Output tokens:     257.6K
Tokens saved:      1.0M (80.0%)
Total exec time:   57m58s (avg 2.3s)
Efficiency meter: ███████████████████░░░░░ 80.0%

By Command
────────────────────────────────────────────────────────────────────────
 #   Command                   Count   Saved    Avg%    Time  Impact
────────────────────────────────────────────────────────────────────────
&lt;span class="p"&gt; 1.&lt;/span&gt;  rtk find                     95  284.0K   62.5%   33.2s  ██████████
&lt;span class="p"&gt; 2.&lt;/span&gt;  rtk lint eslint               4  211.9K   99.5%    8.0s  ███████░░░
&lt;span class="p"&gt; 3.&lt;/span&gt;  rtk curl -s https://r...      1  185.3K   99.9%   277ms  ███████░░░
&lt;span class="p"&gt; 4.&lt;/span&gt;  rtk read                    135  154.0K   13.1%     0ms  █████░░░░░
&lt;span class="p"&gt; 5.&lt;/span&gt;  rtk git diff HEAD -- ...      1   42.3K   85.1%    17ms  █░░░░░░░░░
&lt;span class="p"&gt; 6.&lt;/span&gt;  rtk grep                    217   32.8K   18.6%    79ms  █░░░░░░░░░
&lt;span class="p"&gt; 7.&lt;/span&gt;  rtk ls                      203   24.0K   62.1%     2ms  █░░░░░░░░░
&lt;span class="p"&gt; 8.&lt;/span&gt;  rtk curl -s -X POST h...      2   11.1K   96.5%   653ms  ░░░░░░░░░░
&lt;span class="p"&gt; 9.&lt;/span&gt;  rtk git log --all --o...      1    9.5K   94.7%    10ms  ░░░░░░░░░░
&lt;span class="p"&gt;10.&lt;/span&gt;  rtk curl -fsSL https:...      1    7.2K   96.5%   387ms  ░░░░░░░░░░
────────────────────────────────────────────────────────────────────────
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rtk discover&lt;/code&gt; goes one step further and scans your Claude Code history for commands that would benefit from routing through RTK but aren't yet, so you can widen the net over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;That's the whole setup. Your marketplace holds the plugins, your safety hooks catch the dangerous commands, one command installs everything on a new machine, and two more keep the token bill down. None of these pieces is big on its own. But wired together in a marketplace you own, the next new laptop costs you a couple of commands instead of a lost afternoon. If you've been following along, that starter template is the place to put it all: &lt;a href="https://github.com/Nagell/claude-marketplace-template" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace-template&lt;/a&gt;. If you missed any of the steps, check out other articles in the series.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Install Everything You Need in One Command: Claude Code Plugin Setup</title>
      <dc:creator>Dawid Nitka</dc:creator>
      <pubDate>Tue, 16 Jun 2026 01:41:24 +0000</pubDate>
      <link>https://dev.to/nagell/install-everything-you-need-in-one-command-claude-code-plugin-setup-4gi9</link>
      <guid>https://dev.to/nagell/install-everything-you-need-in-one-command-claude-code-plugin-setup-4gi9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; A Claude Code skill is a &lt;code&gt;SKILL.md&lt;/code&gt; file Claude reads and executes. You can write one that installs your whole plugin lineup across several marketplaces, in the right order, and offers to drop your opinionated &lt;code&gt;CLAUDE.md&lt;/code&gt; into the global config. One frontmatter line keeps it from firing on its own, so it runs only when you call it by name. This article walks through building that skill so a fresh machine goes from zero to fully set up with one command.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Setting up Claude Code on a new machine is a small ritual of forgetting. You reinstall the plugins, then realize you tried to install one before adding its marketplace. Eventually they work, except none of your global defaults are there because you never copied &lt;code&gt;CLAUDE.md&lt;/code&gt; over. An hour later you're set up, and you've already forgotten half the plugins and skills for next time.&lt;/p&gt;

&lt;p&gt;So I moved the whole ritual into a skill. This builds directly on the marketplace from the first article. If you haven't scaffolded one yet, start there. You can pass this article URL straight to Claude Code and follow along.&lt;/p&gt;




&lt;h2&gt;
  
  
  A skill is just a Markdown file
&lt;/h2&gt;

&lt;p&gt;This is the part that surprises people. A Claude Code skill is just a &lt;code&gt;SKILL.md&lt;/code&gt; file in a folder under your plugin's &lt;code&gt;skills/&lt;/code&gt; directory, no special DSL or script format, and Claude reads it as instructions and carries them out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins/your-plugin/
└── skills/
    └── plugin-setup/
        └── SKILL.md         ← this becomes /your-plugin:plugin-setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The folder name becomes the name you call. Everything in the file is a prompt: you write the steps in plain English, and Claude runs them when you invoke &lt;code&gt;/your-plugin:plugin-setup&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you wrote Claude Code commands a while back, this is where they went. Anthropic merged slash commands into skills, so the &lt;code&gt;commands/plugin-setup.md&lt;/code&gt; you might once have written is now &lt;code&gt;skills/plugin-setup/SKILL.md&lt;/code&gt;, invoked exactly the same way (&lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;the docs&lt;/a&gt; lay it out). A skill is the one primitive now.&lt;/p&gt;

&lt;p&gt;That merge matters for setup in particular, because a skill can fire two ways: you call it by name, or Claude loads it on its own when a request looks relevant. The second path is the last thing you want from an installer. You don't need it kicking off because the word "setup" drifted past in some unrelated sentence. One line in the frontmatter, &lt;code&gt;disable-model-invocation: true&lt;/code&gt;, switches the automatic path off, so the skill runs only when you type &lt;code&gt;/your-plugin:plugin-setup&lt;/code&gt; and never decides to install your whole toolchain on a hunch.&lt;/p&gt;

&lt;p&gt;The one below adds marketplaces, installs plugins and walks an interactive selection. You can put far more in a skill than that, and if one grows unwieldy you can split it and pull pieces into their own files.&lt;/p&gt;

&lt;p&gt;The rest of this article builds our sample setup command (skill) piece by piece, then assembles the whole file at the end.&lt;/p&gt;




&lt;h2&gt;
  
  
  The flow, step by step
&lt;/h2&gt;

&lt;p&gt;The skill opens with two setup lines, then does four things in order.&lt;/p&gt;

&lt;p&gt;The setup lines handle environment quirks. When the skill runs as an agent driving the &lt;code&gt;claude&lt;/code&gt; CLI, it prefixes those calls with &lt;code&gt;unset CLAUDECODE &amp;amp;&amp;amp;&lt;/code&gt; to dodge a nested-session error. On Windows, it makes sure &lt;code&gt;CLAUDE_CODE_GIT_BASH_PATH&lt;/code&gt; is set in &lt;code&gt;~/.claude/settings.json&lt;/code&gt; (default &lt;code&gt;C:\Program Files\Git\bin\bash.exe&lt;/code&gt;) before any shell step runs.&lt;/p&gt;

&lt;p&gt;Then the four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask how much to install.&lt;/strong&gt; A clean two-option choice, everything or pick-and-choose, so the skill uses &lt;code&gt;AskUserQuestion&lt;/code&gt;, the structured multiple-choice prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let the user choose (if they asked to).&lt;/strong&gt; Here it deliberately skips &lt;code&gt;AskUserQuestion&lt;/code&gt;. With several marketplaces and a dozen plugins that widget gets clumsy, so the skill prints a numbered list of plugins per marketplace group and takes a plain text reply, one group at a time, before moving on. No clicking through twenty checkboxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install in the right order.&lt;/strong&gt; The step that bites you by hand: a plugin can't install until its marketplace is registered. So the skill adds the marketplaces first, then the plugins, and only adds a marketplace if at least one of its plugins was selected. Pick nothing from a marketplace and it never gets registered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offer the global CLAUDE.md.&lt;/strong&gt; The finishing touch, and the one most setups skip. More on why it's a separate opt-in below.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After the plugins land, the skill tells the user to restart Claude Code, since plugins activate on restart.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the CLAUDE.md step is an explicit offer
&lt;/h2&gt;

&lt;p&gt;Article 1 made a point of this: a &lt;code&gt;CLAUDE.md&lt;/code&gt; inside a plugin folder does nothing on its own. Claude Code's plugin system ignores it. So shipping your global defaults as a plugin file doesn't apply them anywhere.&lt;/p&gt;

&lt;p&gt;The setup skill closes that gap by hand, as its final step: it offers to copy the plugin's &lt;code&gt;CLAUDE.md&lt;/code&gt; into your global &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;. The key word is offer. Your global &lt;code&gt;CLAUDE.md&lt;/code&gt; is personal, and silently overwriting it on a setup run would be hostile. So the skill checks first: it shows you the existing file when there is one, then lets you append, replace or skip. You opt in, every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  What goes in your curated list
&lt;/h2&gt;

&lt;p&gt;The plugin list is the body of the skill. Group it by marketplace and put a one-line description above each plugin as a comment. The skill reads those comments out when it prints the selection menu.&lt;/p&gt;

&lt;p&gt;Treat it as a starting point you'll edit. Marketplaces in one block, plugins grouped under the marketplace they come from. Swap in the ones you actually reach for.&lt;/p&gt;

&lt;p&gt;Full structure below. The reference repo carries my actual, longer list if you want more examples: &lt;a href="https://github.com/Nagell/claude-marketplace" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The complete skill
&lt;/h2&gt;

&lt;p&gt;Here's the whole thing, ready to drop into &lt;code&gt;skills/plugin-setup/SKILL.md&lt;/code&gt;. It wires together every piece above: the agent-mode switch, the Windows path, the four steps, the CLAUDE.md offer and a starter plugin list with two marketplaces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plugin-setup&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install all recommended plugins and marketplaces&lt;/span&gt;
&lt;span class="na"&gt;disable-model-invocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Setup Plugins&lt;/span&gt;

Install recommended Claude Code plugins and marketplaces with interactive selection.

&lt;span class="gu"&gt;## Instructions&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; When running as an agent, prefix all &lt;span class="sb"&gt;`claude`&lt;/span&gt; CLI commands with
   &lt;span class="sb"&gt;`unset CLAUDECODE &amp;amp;&amp;amp;`&lt;/span&gt; to avoid the nested-session error.
&lt;span class="p"&gt;2.&lt;/span&gt; On Windows, check &lt;span class="sb"&gt;`~/.claude/settings.json`&lt;/span&gt; for &lt;span class="sb"&gt;`CLAUDE_CODE_GIT_BASH_PATH`&lt;/span&gt;.
   If missing, set it (default &lt;span class="sb"&gt;`C:\Program Files\Git\bin\bash.exe`&lt;/span&gt;) and ask the
   user to confirm it before continuing.
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Ask install mode**&lt;/span&gt;: use &lt;span class="sb"&gt;`AskUserQuestion`&lt;/span&gt;. "Install all (Recommended)"
   installs everything below; "Let me choose" goes to interactive selection.
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="gs"&gt;**Interactive selection**&lt;/span&gt; (only if "Let me choose"): do NOT use
   &lt;span class="sb"&gt;`AskUserQuestion`&lt;/span&gt; here. For each marketplace group, print a numbered list of
   its plugins, using the comment above each as the description. Ask the user in
   a plain text message to reply with numbers or names, or "all" / "none". Wait
   for the reply before moving to the next group.
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="gs"&gt;**Install order**&lt;/span&gt;: add the marketplaces first, then install the plugins. Only
   add a marketplace if at least one of its plugins was selected.
&lt;span class="p"&gt;6.&lt;/span&gt; After installing, tell the user to restart Claude Code.
&lt;span class="p"&gt;7.&lt;/span&gt; &lt;span class="gs"&gt;**Global CLAUDE.md**&lt;/span&gt;: offer to save this plugin's &lt;span class="sb"&gt;`CLAUDE.md`&lt;/span&gt; to
   &lt;span class="sb"&gt;`~/.claude/CLAUDE.md`&lt;/span&gt;. If it doesn't exist, ask to create it. If it does,
   show both files and ask to append, replace or skip.

&lt;span class="gu"&gt;## Plugin List&lt;/span&gt;

&lt;span class="gu"&gt;### Marketplaces&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;/plugin marketplace add claude-plugins-official
/plugin marketplace add some-author/their-marketplace
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;### Plugins&lt;/span&gt;

&lt;span class="gu"&gt;#### claude-plugins-official&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;&lt;span class="c"&gt;# Feature development workflow with exploration and review agents&lt;/span&gt;
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;feature-dev@claude-plugins-official
&lt;span class="c"&gt;# Composable workflow skills for brainstorming, planning, debugging and TDD&lt;/span&gt;
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;superpowers@claude-plugins-official
&lt;span class="c"&gt;# PR review agents for comments, tests and error handling&lt;/span&gt;
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;pr-review-toolkit@claude-plugins-official
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;#### their-marketplace&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;&lt;span class="c"&gt;# A skill you want available in every project&lt;/span&gt;
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;some-skill@their-marketplace
&lt;span class="p"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save it, install with your plugin, then run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/your-plugin:plugin-setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next article takes this further. Once your tooling installs in one command, the question becomes what that tooling costs you per session, and how to cut overall token usage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Use the starter template to get a clean slate with everything pre-wired: &lt;a href="https://github.com/Nagell/claude-marketplace-template" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace-template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The template gives you one empty plugin, a &lt;code&gt;CLAUDE.md&lt;/code&gt; with sensible defaults, and a GitHub Actions workflow that handles versioning and releases automatically. Hit "Use this template" on GitHub and you're ready to add your first plugin.&lt;/p&gt;

&lt;p&gt;If you want to see a fuller working example with multiple plugins and real hook scripts, &lt;a href="https://github.com/Nagell/claude-marketplace" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace&lt;/a&gt; is the reference repo used throughout this series.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Claude Code Safety: Hooks, Sandboxes, and Running Autonomously Without the Paranoia</title>
      <dc:creator>Dawid Nitka</dc:creator>
      <pubDate>Tue, 16 Jun 2026 01:38:11 +0000</pubDate>
      <link>https://dev.to/nagell/claude-code-safety-hooks-sandboxes-and-running-autonomously-without-the-paranoia-442b</link>
      <guid>https://dev.to/nagell/claude-code-safety-hooks-sandboxes-and-running-autonomously-without-the-paranoia-442b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Claude Code runs Bash, which means it can run dangerous Bash. There are many ways to put guardrails around it. Four well-known ones: write your own &lt;code&gt;PreToolUse&lt;/code&gt; hooks (lightweight, portable, yours), use the built-in &lt;code&gt;/sandbox&lt;/code&gt; (filesystem and network isolation), run inside a Docker microVM (strongest isolation), or grab a community hook collection. This article walks through those four and ends with a copy-paste hook you can drop into any plugin.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Claude was halfway through cleaning up a build directory when it generated &lt;code&gt;rm -rf&lt;/code&gt; with a path that resolved to my home folder. A glob expanded wrong. The command was syntactically fine and about to run.&lt;/p&gt;

&lt;p&gt;It didn't, because a hook caught it and returned exit code 2. Claude saw the rejection and retried with a scoped path instead.&lt;/p&gt;

&lt;p&gt;Safety tooling earns its place by catching the handful of commands that would ruin your day, without getting in the way of the hundred that wouldn't. There are many methods for this. Here are four well-known ones, from "ten lines of Bash" to "full microVM", and when each earns its place.&lt;/p&gt;

&lt;p&gt;You can pass this article URL directly to Claude Code and follow along.&lt;/p&gt;




&lt;h2&gt;
  
  
  The permission model: how Claude Code decides what to run
&lt;/h2&gt;

&lt;p&gt;Before the guardrails, the thing they hook into.&lt;/p&gt;

&lt;p&gt;Every time Claude wants to use a tool, Claude Code runs it through a permission check. Bash commands get matched against your allow and deny rules. In normal mode you approve risky commands manually, one at a time.&lt;/p&gt;

&lt;p&gt;Everyone who has run a real session knows where this goes. Approve, approve, approve, command after command, until the prompting wears you down and you flip on bypass mode just to get work done. And there's the gap. The moment you let Claude run autonomously, the manual approval step disappears, and a wrong command runs with no human in the loop.&lt;/p&gt;

&lt;p&gt;That tradeoff is exactly why the more automated approaches exist. Instead of approving everything or nothing, they let Claude run freely and stop it only in the cases that actually matter. Hooks intercept the command and decide programmatically. Sandboxes constrain what any command can touch. They stack, so you don't have to pick just one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Approach A: Custom hooks - the lightweight, portable option you own
&lt;/h2&gt;

&lt;p&gt;A hook is a script Claude Code runs at a specific point in its lifecycle. The one you want for safety is &lt;code&gt;PreToolUse&lt;/code&gt;: it fires before a tool executes, and its exit code decides what happens next.&lt;/p&gt;

&lt;p&gt;Three outcomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exit &lt;code&gt;0&lt;/code&gt;&lt;/strong&gt; - allow the command. Optionally print JSON with a &lt;code&gt;systemMessage&lt;/code&gt; to warn without blocking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit &lt;code&gt;2&lt;/code&gt;&lt;/strong&gt; - block the command. Whatever you write to stderr gets shown to Claude, so it understands why and can adjust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON decision&lt;/strong&gt; - print a &lt;code&gt;permissionDecision&lt;/code&gt; object to deny with a custom reason, even in bypass mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You wire it up in a &lt;code&gt;hooks.json&lt;/code&gt; file inside your plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${CLAUDE_PLUGIN_ROOT}/hooks/safety-guards.sh"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;matcher&lt;/code&gt; is &lt;code&gt;Bash&lt;/code&gt;, so this only runs for Bash commands. &lt;code&gt;${CLAUDE_PLUGIN_ROOT}&lt;/code&gt; resolves to your plugin's directory, so the path works on any machine.&lt;/p&gt;

&lt;p&gt;The script reads the command from stdin as JSON, extracts it, and checks it against patterns. The skeleton is just the read, a fail-open guard, and one block rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="c"&gt;# Claude Code passes the tool input as JSON on stdin&lt;/span&gt;
&lt;span class="nv"&gt;INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;COMMAND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'s/.*"command"\s*:\s*"\([^"]*\)".*/\1/p'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Fail open: if we can't read a command, don't block&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0

&lt;span class="c"&gt;# BLOCK: rm -rf targeting home or root&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'rm\s+-[a-zA-Z]*rf[a-zA-Z]*\s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'(~[/\s]|\$HOME|/home/[^/\s]+[/\s]|\s+/\s+)'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"BLOCKED: rm -rf targeting home or root directory."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;2
  &lt;span class="k"&gt;fi
fi&lt;/span&gt;

&lt;span class="c"&gt;# ... more block and warn rules go here&lt;/span&gt;

&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full script, with secret detection, the force-push gate and warnings, is at the end of this article.&lt;/p&gt;

&lt;p&gt;Two design choices worth calling out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fail open.&lt;/strong&gt; If the script can't parse a command, it exits &lt;code&gt;0&lt;/code&gt; and lets it through. A safety hook that breaks every Bash call the moment its regex hits an edge case is worse than no hook, because you'll rip it out within a day. So block the clearly dangerous stuff and wave the rest through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Block what's always destructive, warn on what's only sometimes wrong.&lt;/strong&gt; &lt;code&gt;rm -rf ~&lt;/code&gt; is never what you want, so block it. &lt;code&gt;git reset --hard&lt;/code&gt; is sometimes exactly the thing you need, so warn and let Claude decide with the warning sitting right there in context.&lt;/p&gt;

&lt;p&gt;A real guard script grows more patterns over time. Common additions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block &lt;code&gt;.env&lt;/code&gt; writes through the shell (&lt;code&gt;echo ... &amp;gt; .env&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Block &lt;code&gt;git push --force&lt;/code&gt; unless it's &lt;code&gt;--force-with-lease&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Warn on &lt;code&gt;git clean -f&lt;/code&gt;, &lt;code&gt;DROP TABLE&lt;/code&gt;, &lt;code&gt;TRUNCATE&lt;/code&gt;, production keywords&lt;/li&gt;
&lt;li&gt;Gate &lt;code&gt;git push&lt;/code&gt; behind an explicit confirmation token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is portable: a shell script and a JSON file, with no daemon and nothing to install. It rides along in your plugin and works on Linux, macOS, WSL and Git Bash on Windows. There's a tradeoff, of course. It only catches patterns you thought to write ahead of time - a smart filter, with all the holes any filter has.&lt;/p&gt;

&lt;p&gt;A fuller version of this script, with the force-push gate and the full pattern list, lives in the reference repo: &lt;a href="https://github.com/Nagell/claude-marketplace" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Approach B: Native sandbox - built-in filesystem and network isolation
&lt;/h2&gt;

&lt;p&gt;Claude Code ships a sandbox you turn on with &lt;code&gt;/sandbox&lt;/code&gt;. Instead of inspecting commands, it constrains what any command is allowed to do.&lt;/p&gt;

&lt;p&gt;Two layers of isolation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filesystem&lt;/strong&gt; - writes are locked to the current working directory by default. A command can read widely but can't write outside the project unless you allow it. You tune this with &lt;code&gt;allowWrite&lt;/code&gt; and &lt;code&gt;denyWrite&lt;/code&gt; rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network&lt;/strong&gt; - outbound traffic goes through a local proxy with an allowlist. Approved domains pass, everything else is blocked. No surprise calls to a random host.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The payoff is fewer interruptions. Because sandboxed commands can't escape the box, Claude Code stops asking you to approve them one by one. In practice that's a large cut in permission prompts during an autonomous session, which is the whole reason you'd run one.&lt;/p&gt;

&lt;p&gt;Platform support is the catch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt; - uses the OS Seatbelt sandbox, works out of the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux / WSL2&lt;/strong&gt; - uses bubblewrap, which you install yourself (plus socat; Ubuntu 24.04+ needs an AppArmor tweak).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WSL1&lt;/strong&gt; - not supported.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other caveats: Docker and watchman don't play well inside the sandbox, and there's an escape hatch. When a command fails because of sandbox restrictions, Claude can retry it with &lt;code&gt;dangerouslyDisableSandbox&lt;/code&gt;. That retry runs outside the sandbox but goes back through the normal permission flow, so it still asks you. It's not a silent bypass. To remove the hatch entirely, set &lt;code&gt;allowUnsandboxedCommands: false&lt;/code&gt; (Strict sandbox mode in the &lt;code&gt;/sandbox&lt;/code&gt; panel) and the parameter is ignored.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://code.claude.com/docs/en/sandboxing" rel="noopener noreferrer"&gt;code.claude.com/docs/en/sandboxing&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Approach C: Docker microVM - strongest isolation, team-friendly
&lt;/h2&gt;

&lt;p&gt;When hooks and the native sandbox aren't enough, you go up a level: run Claude inside a Docker microVM.&lt;/p&gt;

&lt;p&gt;This isn't a container sharing your host kernel. Each sandbox gets a full microVM with its own kernel and a private Docker daemon. Your dev environment is cloned in, Claude works inside it, and nothing it does touches the host. Worst case, you throw the VM away.&lt;/p&gt;

&lt;p&gt;This is the right call for two situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Teams&lt;/strong&gt; - everyone gets the same isolated environment, and a mistake in one is contained to one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-running autonomous agents&lt;/strong&gt; - if Claude is going to run for hours unattended, the blast radius of any single command should be a disposable VM, not your laptop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cost is setup and overhead. You're running a VM, which is heavier than a hook or the native sandbox. For a quick session it's overkill. But if you're leaving an agent running unattended overnight, it's the only one of the four that lets you actually sleep.&lt;/p&gt;

&lt;p&gt;Official guide: &lt;a href="https://docs.docker.com/ai/sandboxes/agents/claude-code/" rel="noopener noreferrer"&gt;Docker Sandboxes for Claude Code&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Approach D: Community collections - standing on shoulders
&lt;/h2&gt;

&lt;p&gt;You don't have to write every pattern yourself. Several maintained hook collections exist, and they've already hit the edge cases you haven't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;karanb192/claude-code-hooks&lt;/code&gt;&lt;/strong&gt; - blocks the classics: &lt;code&gt;rm -rf ~&lt;/code&gt;, fork bombs, &lt;code&gt;curl | sh&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CodyLunders/claude-code-hooks-library&lt;/code&gt;&lt;/strong&gt; - 55 hooks, 12 security-specific, including AWS credential scanning (&lt;code&gt;AKIA[0-9A-Z]{16}&lt;/code&gt;) and an interactive installer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;disler/claude-code-hooks-mastery&lt;/code&gt;&lt;/strong&gt; - educational, walks through multiple security patterns so you learn the mechanism, not just the config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boucle&lt;/strong&gt; - a standalone safety hook framework for Claude Code: &lt;a href="https://framework.boucle.sh/" rel="noopener noreferrer"&gt;framework.boucle.sh&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also a &lt;a href="https://dev.to/yurukusa/10-claude-code-hooks-i-collected-from-108-hours-of-autonomous-operation-now-open-source-5633"&gt;DEV Community writeup&lt;/a&gt; of 10 hooks pulled from 108 hours of autonomous operation, which is exactly the kind of battle-tested list worth reading before you invent your own.&lt;/p&gt;

&lt;p&gt;So skim one of these, lift the patterns that fit your setup, and drop them into your own guard script. You keep the portability of owning the file, and you skip relearning the lessons someone already paid for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Custom hooks&lt;/th&gt;
&lt;th&gt;Native sandbox&lt;/th&gt;
&lt;th&gt;Docker microVM&lt;/th&gt;
&lt;th&gt;Community hooks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Isolation level&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pattern-based filter&lt;/td&gt;
&lt;td&gt;Filesystem + network&lt;/td&gt;
&lt;td&gt;Full VM, own kernel&lt;/td&gt;
&lt;td&gt;Pattern-based filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup effort&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low (a script + JSON)&lt;/td&gt;
&lt;td&gt;Low-medium (install on Linux)&lt;/td&gt;
&lt;td&gt;High (VM tooling)&lt;/td&gt;
&lt;td&gt;Low (install a collection)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Permission friction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Very low (auto-allow)&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Portability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Excellent (just files)&lt;/td&gt;
&lt;td&gt;OS-dependent&lt;/td&gt;
&lt;td&gt;Needs Docker&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Platforms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Linux, macOS, Windows (Git Bash / WSL)&lt;/td&gt;
&lt;td&gt;macOS and Linux native, Windows via WSL2 (no WSL1)&lt;/td&gt;
&lt;td&gt;Anywhere Docker runs&lt;/td&gt;
&lt;td&gt;Linux, macOS, Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Catches the unknown&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No, only known patterns&lt;/td&gt;
&lt;td&gt;Yes, by construction&lt;/td&gt;
&lt;td&gt;Yes, by construction&lt;/td&gt;
&lt;td&gt;No, only known patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The split that matters is the last row. A hook only catches what you already wrote a pattern for, whereas a sandbox constrains what any command can touch in the first place, so it stops the things you never saw coming too. That's why the strong setups stack them - a hook on top for fast, project-specific rules you can read at a glance, and a sandbox underneath as the backstop for everything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to actually use
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Most developers, most of the time:&lt;/strong&gt; custom hooks. Lightweight, portable and you understand exactly what they do. Start here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frequent autonomous sessions:&lt;/strong&gt; add the native sandbox on top. The interruption cut alone pays for the Linux setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teams or overnight agents:&lt;/strong&gt; Docker microVM. When the blast radius has to be zero, nothing else gets you there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with the hook. Here's one to copy into a plugin's &lt;code&gt;hooks/&lt;/code&gt; directory and wire up through the &lt;code&gt;hooks.json&lt;/code&gt; shown earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# safety-guards.sh - PreToolUse hook for Bash&lt;/span&gt;
&lt;span class="c"&gt;# Exit 0 = allow, exit 2 = block (stderr shown to Claude)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;COMMAND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'s/.*"command"\s*:\s*"\([^"]*\)".*/\1/p'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0

block&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"BLOCKED: &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Destructive filesystem&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'rm\s+-[a-zA-Z]*rf[a-zA-Z]*\s'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'(~[/\s]|\$HOME|/home/[^/\s]+[/\s]|\s+/\s+)'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  block &lt;span class="s2"&gt;"rm -rf targeting home or root"&lt;/span&gt;

&lt;span class="c"&gt;# Hardcoded secrets&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'(API_KEY|SECRET|TOKEN|PASSWORD)=['&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'"]?[a-zA-Z0-9_\-]{16,}'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  block &lt;span class="s2"&gt;"possible hardcoded secret - use env vars"&lt;/span&gt;

&lt;span class="c"&gt;# Unsafe force push&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'git\s+push\s+.*--force[^-]'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'force-with-lease'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  block &lt;span class="s2"&gt;"git push --force without --force-with-lease"&lt;/span&gt;

&lt;span class="c"&gt;# Warn (allow)&lt;/span&gt;
&lt;span class="nv"&gt;WARNINGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'git\s+reset\s+--hard'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;WARNINGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"git reset --hard destroys uncommitted changes"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WARNINGS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;systemMessage&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;SAFETY WARNING: &lt;/span&gt;&lt;span class="nv"&gt;$WARNINGS&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;

&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ship it in your plugin and Claude Code runs it before every Bash call. (The file needs its executable bit, which you set once and commit; git preserves it, so nobody runs &lt;code&gt;chmod&lt;/code&gt; after installing.) Add patterns as you find the ones that bite.&lt;/p&gt;

&lt;p&gt;You can have Claude write these rules for you. If you do, ask it to test them too: have it feed the hook fake commands that should trip each pattern (a dummy &lt;code&gt;rm -rf ~&lt;/code&gt;, a planted secret, a &lt;code&gt;git push --force&lt;/code&gt;) and confirm they actually block. Ask it to check the regex isn't too loose while it's there, so a real command doesn't slip past. It's the fastest way to catch a pattern that's too greedy or too narrow before it catches you.&lt;/p&gt;

&lt;p&gt;That's a safety net you own, in a file you can read in one sitting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Use the starter template to get a clean slate with everything pre-wired: &lt;a href="https://github.com/Nagell/claude-marketplace-template" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace-template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The template gives you one empty plugin, a &lt;code&gt;CLAUDE.md&lt;/code&gt; with sensible defaults, and a GitHub Actions workflow that handles versioning and releases automatically. Hit "Use this template" on GitHub and you're ready to add your first plugin.&lt;/p&gt;

&lt;p&gt;If you want to see a fuller working example with multiple plugins and real hook scripts, &lt;a href="https://github.com/Nagell/claude-marketplace" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace&lt;/a&gt; is the reference repo used throughout this series.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Build Your Own Claude Code Marketplace: Scaffold, Structure, and Auto-Updates</title>
      <dc:creator>Dawid Nitka</dc:creator>
      <pubDate>Sun, 14 Jun 2026 16:52:08 +0000</pubDate>
      <link>https://dev.to/nagell/build-your-own-claude-code-marketplace-scaffold-structure-and-auto-updates-4n3f</link>
      <guid>https://dev.to/nagell/build-your-own-claude-code-marketplace-scaffold-structure-and-auto-updates-4n3f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; A Claude Code marketplace is a GitHub repo with a specific folder structure. You create plugins inside it, push to GitHub, and anyone (including yourself) can install your plugins with two commands. This article shows you how to scaffold one from zero and add your first plugin. A one-command setup script comes later in the series, and auto-versioning plus release CI come pre-wired as a bonus in the starter template.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Every time I switched between my work machine and home setup, or got a new laptop, I'd lose an hour reinstalling the same Claude Code plugins and setting up the same tools. Wrong versions, configs I'd forgotten to copy over, the whole thing rebuilt from nothing. So I built a marketplace - a single GitHub repo that holds all my plugins and keeps them versioned.&lt;/p&gt;

&lt;p&gt;Before we get into the structure: you can skip the scaffold entirely and use the starter template directly (link below). The rest of this article explains its structure.&lt;/p&gt;

&lt;p&gt;Template: &lt;a href="https://github.com/Nagell/claude-marketplace-template" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace-template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can pass this article URL directly to Claude Code and follow along. &lt;/p&gt;




&lt;h2&gt;
  
  
  What is a Claude Code marketplace?
&lt;/h2&gt;

&lt;p&gt;A marketplace is a GitHub repository with a &lt;code&gt;marketplace.json&lt;/code&gt; registry that Claude Code reads to discover plugins. Each plugin lives in its own subdirectory and has a &lt;code&gt;plugin.json&lt;/code&gt; describing it.&lt;/p&gt;

&lt;p&gt;There's no npm package and nothing to host, just a public repo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Directory structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your-marketplace/
├── .claude-plugin/
│   └── marketplace.json        ← the plugin registry
├── plugins/
│   └── your-plugin/
│       ├── .claude-plugin/
│       │   └── plugin.json     ← plugin metadata
│       ├── hooks/
│       │   ├── hooks.json      ← hook wiring (which hooks fire when)
│       │   └── your-guard.sh   ← the scripts hooks.json points to
│       ├── skills/             ← SKILL.md files (auto-loaded, or run with /name)
│       ├── agents/             ← agent definitions
│       └── .mcp.json           ← MCP server config
├── .github/
│   └── workflows/
│       └── release.yml
├── scripts/
│   └── generate-release-config.js
└── CLAUDE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A plugin can contain any combination of these. Start with what you actually use - you don't need all of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  What can a plugin do?
&lt;/h2&gt;

&lt;p&gt;Quite a bit more than you'd expect. A plugin can ship:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; - &lt;code&gt;SKILL.md&lt;/code&gt; files that give Claude instructions for a domain. Claude loads one on its own when it's relevant, or you run it by name with &lt;code&gt;/your-plugin:skill-name&lt;/code&gt;. A skill can be passive reference (a testing strategy, a code-review approach) or an action you trigger by hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks&lt;/strong&gt; - Scripts that run before or after Claude uses a tool. Common uses: blocking dangerous shell commands, auto-formatting files after edits, logging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents&lt;/strong&gt; - Named subagents with specific roles and tool access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP servers&lt;/strong&gt; - Registered via &lt;code&gt;.mcp.json&lt;/code&gt;, giving Claude access to external tools and APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A skill isn't limited to passive knowledge. It's Markdown that Claude executes, so a single one can install a package manager, configure your terminal, set up Nerd Fonts, pull files from a repo, walk a whole setup through and so on.&lt;/p&gt;




&lt;h2&gt;
  
  
  A note on CLAUDE.md inside a plugin
&lt;/h2&gt;

&lt;p&gt;You'll notice the plugin folder structure doesn't include a &lt;code&gt;CLAUDE.md&lt;/code&gt;. That's intentional.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;CLAUDE.md&lt;/code&gt; inside a plugin folder is &lt;strong&gt;not&lt;/strong&gt; a recognized plugin component - Claude Code's plugin system ignores it. If you want a plugin to ship AI instructions, &lt;code&gt;SKILL.md&lt;/code&gt; files in a &lt;code&gt;skills/&lt;/code&gt; directory are the correct mechanism. A plugin-level &lt;code&gt;CLAUDE.md&lt;/code&gt; has no automatic effect.&lt;/p&gt;

&lt;p&gt;That said, there's a useful pattern: a plugin can ship a &lt;strong&gt;skill&lt;/strong&gt; that explicitly offers to copy an opinionated &lt;code&gt;CLAUDE.md&lt;/code&gt; into the user's global &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;. The user opts in, the skill handles the copy, and the result is a consistent global configuration across machines. We'll cover exactly this in the article about one-command setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  The marketplace.json
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.claude-plugin/marketplace.json&lt;/code&gt; is the registry file Claude Code reads when someone adds your marketplace. Here's what it looks like when you're done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-marketplace-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What your marketplace is for"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-plugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./plugins/your-plugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"relevant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll manage this file yourself as you add plugins - updating the list and bumping versions when things change. The starter template automates that for you: a CI script syncs this registry from your individual &lt;code&gt;plugin.json&lt;/code&gt; files on every push, so you never have to touch it by hand, and auto-versioning from conventional commits rides along with it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating your first plugin
&lt;/h2&gt;

&lt;p&gt;A plugin needs exactly one file to exist: &lt;code&gt;plugins/your-plugin-name/.claude-plugin/plugin.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-plugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What this plugin does"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"you@example.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"relevant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the minimum. Create the subdirectories for whatever components you're shipping (hooks, skills, agents) alongside it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Publish on GitHub
&lt;/h2&gt;

&lt;p&gt;Push the repo. It needs to be public - Claude Code fetches marketplaces directly from GitHub.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install from your marketplace
&lt;/h2&gt;

&lt;p&gt;Two commands inside Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin marketplace add your-username/your-marketplace-repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This registers the marketplace with Claude Code. Replace &lt;code&gt;your-username/your-marketplace-repo&lt;/code&gt; with your GitHub username and repo name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin install your-plugin-name@your-marketplace-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs a specific plugin. The format is &lt;code&gt;&amp;lt;plugin-name&amp;gt;@&amp;lt;marketplace-name&amp;gt;&lt;/code&gt; - the marketplace name comes from the &lt;code&gt;name&lt;/code&gt; field in your &lt;code&gt;marketplace.json&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Auto-versioning and CI
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bonus in the template:&lt;/strong&gt; The starter template ships a GitHub Actions workflow and a sync script that drive version bumps from conventional commits (&lt;code&gt;feat:&lt;/code&gt; → minor, &lt;code&gt;fix:&lt;/code&gt; → patch) and keep &lt;code&gt;marketplace.json&lt;/code&gt; in sync automatically. You don't build any of it - it's wired up and ready the moment you start from the template.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Under the hood it uses the &lt;a href="https://github.com/googleapis/release-please-action" rel="noopener noreferrer"&gt;Release Please GitHub Action&lt;/a&gt; and a small Node.js script. (The Action wraps the &lt;a href="https://github.com/googleapis/release-please" rel="noopener noreferrer"&gt;Release Please&lt;/a&gt; tool, which is where the docs live.) Conventional commits on &lt;code&gt;main&lt;/code&gt; create a Release PR; merging it publishes a tagged GitHub Release.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enable auto-updates
&lt;/h2&gt;

&lt;p&gt;Once your marketplace is live, Claude Code can check for plugin updates automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Via the UI:&lt;/strong&gt; run &lt;code&gt;/plugin&lt;/code&gt; → Marketplaces tab → select your marketplace → Enable "Auto-update". After an update, Claude will prompt you to run &lt;code&gt;/reload-plugins&lt;/code&gt;. No full restart required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternatively - edit settings.json directly&lt;/strong&gt; (useful if you're setting this up by hand or passing it to an agent):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extraKnownMarketplaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/your-username/your-marketplace-repo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"autoUpdate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Use the starter template to get a clean slate with everything pre-wired: &lt;a href="https://github.com/Nagell/claude-marketplace-template" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace-template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The template gives you one empty plugin, a &lt;code&gt;CLAUDE.md&lt;/code&gt; with sensible defaults, and a GitHub Actions workflow that handles versioning and releases automatically. Hit "Use this template" on GitHub and you're ready to add your first plugin.&lt;/p&gt;

&lt;p&gt;If you want to see a fuller working example with multiple plugins and real hook scripts, &lt;a href="https://github.com/Nagell/claude-marketplace" rel="noopener noreferrer"&gt;github.com/Nagell/claude-marketplace&lt;/a&gt; is the reference repo used throughout this series.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
    <item>
      <title>Creating a scalable Monorepo for Vue - Nx</title>
      <dc:creator>Dawid Nitka</dc:creator>
      <pubDate>Sun, 01 Jun 2025 13:00:02 +0000</pubDate>
      <link>https://dev.to/nagell/creating-a-scalable-monorepo-for-vue-nx-59il</link>
      <guid>https://dev.to/nagell/creating-a-scalable-monorepo-for-vue-nx-59il</guid>
      <description>&lt;p&gt;As your codebase grows, so does the complexity of managing multiple apps and libraries. At this point Nx can help with its &lt;a href="https://nx.dev/features/explore-graph" rel="noopener noreferrer"&gt;graph&lt;/a&gt;, but more importantly it can orchestrate all builds and tests in monorepo and keep you sane while managing dozens of packages. &lt;/p&gt;

&lt;p&gt;In essence Nx is capable of running tasks in parallel based on what has changed from the previous commit. It also respects dependencies, ensuring that libraries needed by others are built first.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Project Graph &amp;amp; Dependency Awareness&lt;/strong&gt; &lt;br&gt;
Nx automatically analyzes your workspace and builds a project graph, understanding how your apps and libraries depend on each other. This &lt;br&gt;
allows you to build or test only what has changed and furthermore what is directly influenced by it, based on the changes between the current and the previous commit.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code Generation&lt;/strong&gt;&lt;br&gt;
Nx provides some nice generators for scaffolding new apps, libraries, &lt;br&gt;
etc. I found them to be decent for Vue, but if you like it your way you can of course just ignore them. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Caching&lt;/strong&gt;&lt;br&gt;
Nx caches build and test results, so repeated commands are lightning &lt;br&gt;
fast. It can even share cache results across CI runs and between developers (Caution: sharing is a nice but paid feature - still very useful for enterprise clients).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Powerful CI/CD&lt;/strong&gt;&lt;br&gt;
Nx can run tasks in parallel, only for affected projects, and provides detailed output for CI pipelines.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Configure Nx in Your Monorepo
&lt;/h2&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Nx
&lt;/h3&gt;

&lt;p&gt;If you’re starting from scratch, you can create a new Nx workspace with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpx create-nx-workspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In an existing monorepo, install Nx as a dev dependency and run &lt;code&gt;init&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; nx

nx init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Nx Workspace Structure
&lt;/h3&gt;

&lt;p&gt;Nx expects some sub-directories with &lt;code&gt;package.json&lt;/code&gt; or it's internal &lt;code&gt;project.json&lt;/code&gt; file by default as a marker of a package. Sample structure can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/
├── apps/
│   └── app_1/
├── libs/
│   └── commons/
│   └── ui/
├── nx.json
├── workspace.json (or project.json or package.json files)
├── tsconfig.base.json
├── pnpm-workspace.yaml
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;apps/&lt;/strong&gt;: Applications (SPA, SSR, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;libs/&lt;/strong&gt;: Reusable libraries (UI, utilities, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nx.json&lt;/strong&gt;: Nx-specific configuration (project graph, tasks, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;workspace.json&lt;/strong&gt;: Project definitions (can be split into per-project &lt;code&gt;project.json&lt;/code&gt; or &lt;code&gt;package.json&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pnpm-workspace.yaml&lt;/strong&gt;: Defines workspace packages for pnpm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Nx Plugins
&lt;/h3&gt;

&lt;p&gt;Nx supports plugins for frameworks and tools. For example, to add Vue support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; @nx/vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or for Storybook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; @nx/storybook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the mentioned plugins you can run and utilize Nx commands for these frameworks, but also use them for scaffolding new projects.&lt;/p&gt;

&lt;p&gt;To start with using commands as well as Nx packages I recommend the vscode plugin &lt;a href="https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console" rel="noopener noreferrer"&gt;Nx Console&lt;/a&gt;. It’s a great tool for exploring available options and features.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Nx Commands
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run a target:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  pnpm nx build:staging @monorepo/app_1
  pnpm nx lint @monorepo/commons
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run for affected (changed from the last commit) projects only:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  pnpm nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; build:staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start the project graph:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  pnpm nx graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;List of installed and available plugins:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  pnpm nx list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;graph&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;It helps you visualize the dependencies between your packages. If you want to categorize your packages correctly in this browser based tool, I recommend either generating &lt;code&gt;workspace.json&lt;/code&gt; or a bunch of &lt;code&gt;project.json&lt;/code&gt; files, or as in my case (the simplest one) just adding this to your &lt;code&gt;package.json&lt;/code&gt; files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@monorepo/ui"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"projectType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"library"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// or "application"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dl6yzmdfsv8jgwhr9ul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dl6yzmdfsv8jgwhr9ul.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;affected&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Running locally the &lt;code&gt;affected&lt;/code&gt; command will work with default settings only if creating branch and making some changes as it compares your branch to the &lt;code&gt;main&lt;/code&gt;. But you can always explicitly compare the current state with a state from a couple of commits ago for test purposes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run lint for packages which changed &lt;/span&gt;
&lt;span class="c"&gt;# between the current state and the state from 5 commits ago&lt;/span&gt;
pnpm nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; lint &lt;span class="nt"&gt;--base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;HEAD~5 &lt;span class="nt"&gt;--head&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;HEAD  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8hleux4vwvev7vqbz9d4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8hleux4vwvev7vqbz9d4.png" alt="Nx lint run result" width="737" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the result of running lint for all packages in my monorepo template. &lt;/p&gt;

&lt;p&gt;Not very spectacular, I know, but imagine how much build time you can save with a big monorepo while running this command on your server and executing commands in parallel only for the changed parts!&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the last article in this series. If you want to learn more about creation of monorepo with Vue, check out other tutorials. If you prefer starting with a solid foundation - try this &lt;a href="https://github.com/Nagell/monorepo_template" rel="noopener noreferrer"&gt;template&lt;/a&gt;, where I’ve put all of this into practice.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>vue</category>
      <category>nx</category>
      <category>pnpm</category>
    </item>
    <item>
      <title>Creating a scalable Monorepo for Vue - Shared configs</title>
      <dc:creator>Dawid Nitka</dc:creator>
      <pubDate>Sat, 22 Feb 2025 12:20:09 +0000</pubDate>
      <link>https://dev.to/nagell/creating-a-scalable-monorepo-for-vue-shared-configs-2bfo</link>
      <guid>https://dev.to/nagell/creating-a-scalable-monorepo-for-vue-shared-configs-2bfo</guid>
      <description>&lt;p&gt;Now that we have in place all needed packages (libs and apps) and established connections between them, we have to take care of required configs like &lt;code&gt;vite.config.ts&lt;/code&gt;, &lt;code&gt;.eslint.cjs&lt;/code&gt; and &lt;code&gt;postcss.config.cjs&lt;/code&gt;. We could add all of them multiple times, but as we have monorepo let's use it to our advantage.&lt;/p&gt;

&lt;p&gt;You can create a global &lt;code&gt;configs&lt;/code&gt; directory in the root of monorepo and add all common configs there, like: &lt;code&gt;vite.application.ts&lt;/code&gt;, &lt;code&gt;vite.service.ts&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Later you can import them into apps and libs in the config files and refine them with some overwrites. Of course the overwriting or rather merging step can be a bit problematic. Luckily there are already tested solutions out there like &lt;a href="https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge/48218209#48218209" rel="noopener noreferrer"&gt;this nice merge function&lt;/a&gt; made by John Hildenbiddle.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Merge sample
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Recursively merges the specified object instances
 * @param instances Instances to merge, from left to right
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mergeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Merge an instance with another one
 * @param target Object to merge the custom values into
 * @param source Object with custom values
 * @author inspired by [jhildenbiddle](https://stackoverflow.com/a/48218209).
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mergeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceValue&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceValue&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;targetValue&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;sourceValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sourceValue&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this you can add your &lt;code&gt;vite.application.ts&lt;/code&gt; template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;nxViteTsPaths&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nx/vite/plugins/nx-tsconfig-paths.plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;vue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nf"&gt;vue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;nxViteTsPaths&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;

    &lt;span class="c1"&gt;// Uncomment this if you are using workers.&lt;/span&gt;
    &lt;span class="c1"&gt;// worker: {&lt;/span&gt;
    &lt;span class="c1"&gt;//     plugins: () =&amp;gt; [ nxViteTsPaths() ],&lt;/span&gt;
    &lt;span class="c1"&gt;// },&lt;/span&gt;

    &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;reportCompressedSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;commonjsOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;transformMixedEsModules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the &lt;code&gt;index.ts&lt;/code&gt; file collecting all the configs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./merge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;getProxyConfiguration&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./proxyConfig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;applicationConfiguration&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./vite.application&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getProxyConfiguration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Returns Vite build configuration for client applications,
 * optionally amended with the specified options
 * @param options Custom build options
 * @returns Vite build configuration
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getApplicationConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;applicationConfiguration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Returns Vite build configuration amended with the specified options
 * @param configuration Default build options
 * @param options Custom build options
 * @returns Vite build configuration
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="c1"&gt;// Default configuration&lt;/span&gt;
        &lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// Custom options to override the default configuration&lt;/span&gt;
        &lt;span class="nx"&gt;options&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Handy when you need to peek into that final build configuration&lt;/span&gt;
    &lt;span class="c1"&gt;// console.warn(JSON.stringify(result, null, 2))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally the usage of the whole template can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eslint-disable-next-line&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getApplicationConfiguration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getProxyConfiguration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./../../config/vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;getApplicationConfiguration&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;cacheDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../node_modules/.vite/apps/app_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5005&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// sample proxy configuration&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getProxyConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://localhost:7148/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5005&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../dist/apps/app_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that instead of standard &lt;code&gt;export default {&amp;nbsp;… }&lt;/code&gt; we are using the &lt;code&gt;getApplicationConfiguration(…)&lt;/code&gt;. It allows us to use the predefined template and overwrite some specific parts.&lt;/p&gt;

&lt;p&gt;Check out the implementation in the sample &lt;a href="https://github.com/Nagell/monorepo_template/tree/main/config/vite" rel="noopener noreferrer"&gt;repo&lt;/a&gt;. Usage sample can be found in the &lt;a href="https://github.com/Nagell/monorepo_template/blob/main/apps/app_1/vite.config.ts" rel="noopener noreferrer"&gt;app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Eslint, postcss, etc
&lt;/h2&gt;

&lt;p&gt;The same applies to other repeatable configs like &lt;code&gt;eslint&lt;/code&gt;. No matter if you are using the old syntax (8.x.x) or the newer one using &lt;code&gt;export default&lt;/code&gt; (9.x.x) - you can still add a bunch of custom configs like I did &lt;a href="https://github.com/Nagell/monorepo_template/tree/main/config/eslint" rel="noopener noreferrer"&gt;here&lt;/a&gt; to import them as needed in the apps and libs (&lt;a href="https://github.com/Nagell/monorepo_template/blob/main/apps/app_1/.eslintrc.cjs" rel="noopener noreferrer"&gt;sample&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Generally speaking, in monorepo you can make your life easier by reusing many common configs by sharing them. After a bit of initial cost, adding yet another app or lib will be much easier.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to learn more about creation of monorepo with Vue, check out other tutorials in the series. If you prefer starting with a solid foundation - try this &lt;a href="https://github.com/Nagell/monorepo_template" rel="noopener noreferrer"&gt;template&lt;/a&gt;, where I’ve put all of this into practice.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the next one, we will talk about builds and parallelization with Nx. &lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>vue</category>
      <category>nx</category>
      <category>pnpm</category>
    </item>
    <item>
      <title>Creating a scalable Monorepo for Vue - Libs vs Apps</title>
      <dc:creator>Dawid Nitka</dc:creator>
      <pubDate>Sat, 25 Jan 2025 18:00:12 +0000</pubDate>
      <link>https://dev.to/nagell/creating-a-scalable-monorepo-for-vue-libs-vs-apps-489l</link>
      <guid>https://dev.to/nagell/creating-a-scalable-monorepo-for-vue-libs-vs-apps-489l</guid>
      <description>&lt;p&gt;One of the steps in creating a monorepo is splitting apps and libraries. But how to take them apart? What is a library and do you have to publish it somewhere?&lt;/p&gt;

&lt;p&gt;The simplest answer is that a library is reusable code. To name a few, it can be your UI library, API files that will be reused, or even some library with useful functions—our well-known: &lt;code&gt;helperThis&lt;/code&gt;, &lt;code&gt;helperThat&lt;/code&gt;, ... In the case of Vue, it can also include a bunch of compostables (ex. &lt;code&gt;useSomething&lt;/code&gt;). There's no need to publish them anywhere. They will be used directly from your libraries.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the consumer (app)
&lt;/h2&gt;

&lt;p&gt;Let's assume you already have a couple of candidates to put them outside your app and reuse them. The next question is how to connect them.&lt;/p&gt;

&lt;p&gt;In the previous post I mentioned adding libraries to the &lt;code&gt;package.json&lt;/code&gt; (sample below) file, but it's only one side of the story.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@monorepo/app_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@monorepo/commons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workspace:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@monorepo/ui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workspace:*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the provider (library)
&lt;/h2&gt;

&lt;p&gt;The other side which is the library has to define all files that should be approachable from outside. Of course, it can be just some index file, but it could be a sub-directory as well - there's no need to set every file independently. All this has to be put in the library's &lt;code&gt;package.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@monorepo/commons"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/index.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"./composables/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/composables/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Typescript and intellisense
&lt;/h2&gt;

&lt;p&gt;The sample above should give you already an idea of how to connect things. To use it you can just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;someHelper&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@monorepo/commons/composables/analytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will still notice that TypeScript running in the background cannot recognize imported libraries. To solve this add explicit such paths to the project's &lt;code&gt;tsconfig.json&lt;/code&gt; file (section &lt;code&gt;paths&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"@monorepo/commons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"../../libs/commons/src/*"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first part is what you want to use during the import and the second is a direct path to the file or directory.&lt;/p&gt;

&lt;p&gt;As a bonus it also makes Vue components show you all expected parameters in the form of intellisense as if they were added directly to your project.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up custom paths
&lt;/h3&gt;

&lt;p&gt;I explicitly set &lt;code&gt;@monorepo/commons&lt;/code&gt; to point to the library's &lt;code&gt;/src&lt;/code&gt; directory. If the &lt;code&gt;/src&lt;/code&gt; directory is exported in the library's &lt;code&gt;package.json&lt;/code&gt;, it's enough to make it work.&lt;/p&gt;

&lt;p&gt;You can also define your own, so-to-say, "nicknames". This practice is often used to create paths that are shorter and thus easier to use and remember.&lt;/p&gt;

&lt;p&gt;For example, instead of using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;someHelper&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@monorepo/commons/composables/analytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you could use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;someHelper&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@monorepo/commons/analytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To achieve this, define such a shortcut in the library's &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@monorepo/commons"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/index.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"./composables/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/composables/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"./analytics/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/composables/analytics/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and also add it to the app's &lt;code&gt;tsconfig.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"@monorepo/commons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"../../libs/commons/src/*"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"@monorepo/commons/composables/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"../../libs/commons/src/composables/*"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"@monorepo/commons/analytics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"../../libs/commons/src/composables/analytics/*"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to learn more about creation of monorepo with Vue, check out other tutorials in the series. If you prefer starting with a solid foundation - try this &lt;a href="https://github.com/Nagell/monorepo_template" rel="noopener noreferrer"&gt;template&lt;/a&gt;, where I’ve put all of this into practice.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>vue</category>
      <category>nx</category>
      <category>pnpm</category>
    </item>
    <item>
      <title>Creating a scalable Monorepo for Vue - Workspaces</title>
      <dc:creator>Dawid Nitka</dc:creator>
      <pubDate>Tue, 14 Jan 2025 21:56:28 +0000</pubDate>
      <link>https://dev.to/nagell/creating-a-scalable-monorepo-for-vue-workspaces-5dlk</link>
      <guid>https://dev.to/nagell/creating-a-scalable-monorepo-for-vue-workspaces-5dlk</guid>
      <description>&lt;p&gt;In the previous post, I mentioned so-called workspaces. This is a good starting point which also will show how to structure your future and existing projects. The first one (starting without any projects) is easy, the second (existing ones) requires a bit more work but with a bit of luck, everything should run smoothly.&lt;/p&gt;

&lt;p&gt;The below steps describe how to do this with &lt;a href="https://pnpm.io/workspaces" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt;. You could also use &lt;a href="https://yarnpkg.com/features/workspaces" rel="noopener noreferrer"&gt;yarn berry&lt;/a&gt; or &lt;a href="https://classic.yarnpkg.com/lang/en/docs/workspaces/" rel="noopener noreferrer"&gt;yarn classic&lt;/a&gt;. In such case this approach works slightly differently by adding such structure description to the &lt;code&gt;package.json&lt;/code&gt; file. The rest is quite similar.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait, but why not npm? I've heard that it also has workspaces.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AFAIK it's still not monorepo-ready due to serious problems with hoisting and the lack of ability to solve such dependencies. A good explanation of the struggle with npm caveats is provided in &lt;a href="https://medium.com/@d.ts/how-to-use-npm-workspace-d155076da956" rel="noopener noreferrer"&gt;this article&lt;/a&gt; ;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to know more about creating a monorepo check out other tutorials in the series. If you prefer testing things on your own - try this &lt;a href="https://github.com/Nagell/monorepo_template" rel="noopener noreferrer"&gt;template&lt;/a&gt;, where I put all of this into work.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get to the work
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install pnpm if you don't have it yet:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; pnpm
&lt;span class="c"&gt;#or&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; corepack@latest
corepack &lt;span class="nb"&gt;enable &lt;/span&gt;pnpm
corepack use pnpm@latest-10
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;or you can follow the other options from the &lt;a href="https://pnpm.io/installation" rel="noopener noreferrer"&gt;pnpm installation guide&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new directory with a &lt;code&gt;package.json&lt;/code&gt; file structured as follows:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_company_or_so"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;// all repeating dependencies&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; file in the root directory to define your workspace structure:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;apps/*'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;libs/*'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;whatever_else/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a bunch of sub-directories like &lt;code&gt;apps&lt;/code&gt;, &lt;code&gt;libs&lt;/code&gt;, etc. as defined in your workspace file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Put your projects into the mentioned directories and/or create new ones.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cut out the repeating &lt;code&gt;dependencies&lt;/code&gt; and &lt;code&gt;devDependencies&lt;/code&gt; from your projects' &lt;code&gt;package.json&lt;/code&gt; files in the sub-directories and place them in the root &lt;code&gt;package.json&lt;/code&gt; file. These are now connected to all your projects - exceptions are possible (read below).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Delete all your &lt;code&gt;node_modules&lt;/code&gt; in the sub-directories if you copied them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As mentioned in the previous post, you can directly connect your libraries to the projects by modifying the app's &lt;code&gt;package.json&lt;/code&gt; file like so (more on this in the next article):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@monorepo/app_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@monorepo/commons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workspace:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@monorepo/ui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workspace:*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to the root directory and install dependencies for all the projects at once:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm i
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;What about the dependencies that are not identical?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First consider bringing them to the same version, preferably the newer ones.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's too time-consuming right now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I get it—deadlines, and so on. You can just leave them where they are. I would still strongly recommend having only one version of Vue, React, Typescript, etc., because the more packages tied to the same version, the easier it is to update all your projects at once and deal with refactors after updating all the projects in one go.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fine, I updated what I could, but there are still some dependencies requiring different versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's fine, there is nothing wrong with it. Decide which version is more common and move this one to the root &lt;code&gt;package.json&lt;/code&gt;. The less common version will stay in the &lt;code&gt;package.json&lt;/code&gt; file of the project that is using it. This way, you signal your package manager that you need that specific version in a particular project, and it will be installed independently.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What about dependencies that are used only once? Should I move them as well?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's up to you. If you assume that they will be used more often, sure, move them. Are they used only once for this specific project, and it's highly unlikely that it will change? Nah, you don't have to move it and pollute the root &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding new dependencies
&lt;/h2&gt;

&lt;p&gt;If you need to add something new to your monorepo you have two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Install a package for the entire workspace - run it in the root directory&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# for dependencies &lt;/span&gt;
pnpm add &amp;lt;package-name&amp;gt; &lt;span class="nt"&gt;-w&lt;/span&gt;

&lt;span class="c"&gt;# for devDependencies&lt;/span&gt;
pnpm add &amp;lt;package-name&amp;gt; &lt;span class="nt"&gt;-Dw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install it only for one of the apps / libs - run it in the app / lib directory&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# for dependencies&lt;/span&gt;
pnpm add &amp;lt;package-name&amp;gt;

&lt;span class="c"&gt;# for devDependencies&lt;/span&gt;
pnpm add &amp;lt;package-name&amp;gt; &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;I have some problems with dependencies with different versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can try omitting this one migration to the root &lt;code&gt;package.json&lt;/code&gt; file and keeping it in the projects' &lt;code&gt;package.json&lt;/code&gt; files instead.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to learn more about creation of monorepo with Vue, check out other tutorials in the series. If you prefer starting with a solid foundation - try this &lt;a href="https://github.com/Nagell/monorepo_template" rel="noopener noreferrer"&gt;template&lt;/a&gt;, where I’ve put all of this into practice.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>vue</category>
      <category>nx</category>
      <category>pnpm</category>
    </item>
    <item>
      <title>Creating a scalable Monorepo for Vue - Intro</title>
      <dc:creator>Dawid Nitka</dc:creator>
      <pubDate>Tue, 14 Jan 2025 21:29:45 +0000</pubDate>
      <link>https://dev.to/nagell/creating-a-scalable-monorepo-for-vue-intro-4nnm</link>
      <guid>https://dev.to/nagell/creating-a-scalable-monorepo-for-vue-intro-4nnm</guid>
      <description>&lt;p&gt;Hello world,&lt;/p&gt;

&lt;p&gt;which means that this is my first article — first of many — as I intend to write a series on creating a monorepo structure for Vue.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  What problem are we trying to solve?
&lt;/h2&gt;

&lt;p&gt;Vue has become quite popular over the years, and more mid-size companies — and even some big names like Nintendo, Adobe, or Gitlab to name a few — are adopting it. Sometimes, they even choose it as the primary Framework for their products. That's why growing codebases containing many Vue packages are becoming common.&lt;/p&gt;

&lt;p&gt;Juggling with projects and packages, publishing, updating, and dealing with the consequences of postponing the last step longer than we intended, due to fast-paced workflows. Many of us were already there, or are right now. This raises the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is there a good alternative and what are the trade-offs?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this series, I'll try to deliver some answers, as well as provide one of the solutions which you can see complete in this &lt;a href="https://github.com/Nagell/monorepo_template" rel="noopener noreferrer"&gt;template&lt;/a&gt;. Feel free to use it as a base to kickstart your project or simply try it out.&lt;/p&gt;

&lt;p&gt;If you have any questions, feel free to leave a comment or add a &lt;a href="https://github.com/Nagell/monorepo_template/issues/new?labels=enhancement&amp;amp;template=feature-request---.md" rel="noopener noreferrer"&gt;suggestion&lt;/a&gt; in the sample repo.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Monorepo
&lt;/h2&gt;

&lt;p&gt;There is at least one good option on which we will focus as it's definitely a mature and battle-proven one - monorepo.&lt;/p&gt;

&lt;p&gt;In contrary to microservices, which have a bunch of additional complexity this one embraces strengths of a monolith structure. I recommend a very good article on that matter from Maxi Ferreira - &lt;a href="https://frontendatscale.com/issues/45/" rel="noopener noreferrer"&gt;May I Interest You In a Modular Monolith?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The simple explanation is that we put everything into one directory. That's it? Well... not exactly. There are many additional possibilities, but let's start with the easy stuff.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  One repo, but not much more
&lt;/h3&gt;

&lt;p&gt;In the simplest form, it means that we create one repository with a bunch of sub-directories for our projects and libraries (packages) and that's it. The rest stays as it is: pushing to the origin, publishing with &lt;a href="https://docs.npmjs.com/cli/v11/commands/npm-publish" rel="noopener noreferrer"&gt;npm&lt;/a&gt; or &lt;a href="https://lerna.js.org/docs/features/version-and-publish" rel="noopener noreferrer"&gt;Lerna&lt;/a&gt;. Maybe even with &lt;a href="https://github.com/semantic-release/semantic-release" rel="noopener noreferrer"&gt;semantic-release&lt;/a&gt; if you've already made some optimization of the process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Many projects in one place -&amp;gt; less opened windows&lt;/li&gt;
&lt;li&gt;Better overview of your team's work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More branches -&amp;gt; potential chaos in the git tree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add workspace
&lt;/h3&gt;

&lt;p&gt;But what about managing external dependencies and consuming your libraries (packages)?&lt;/p&gt;

&lt;p&gt;If you don't want to go to every project and update dependencies manually you should consider so-called workspaces. More about them in the next article in the series. In short, they will allow you to update more, maybe even all dependencies at once, and still keep full control over your projects, when some more granular interference will be required.&lt;/p&gt;

&lt;h4&gt;
  
  
  Sample workspace configuration with pnpm
&lt;/h4&gt;

&lt;p&gt;To enable workspace features in pnpm, you need a &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; file at the root of your repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;apps/*'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;libs/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file will tell pnpm to treat all projects in the &lt;code&gt;apps&lt;/code&gt; and &lt;code&gt;libs&lt;/code&gt; directories as workspace packages.&lt;br&gt;&lt;br&gt;
More about it in the &lt;a href="https://pnpm.io/workspaces" rel="noopener noreferrer"&gt;pnpm workspace docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://pnpm.io/" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt;, you can manage workspaces and dependencies across all your projects in monorepo.&lt;br&gt;
A couple of basic commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add a dependency to all projects in the workspace&lt;/span&gt;
pnpm add &amp;lt;package-name&amp;gt; &lt;span class="nt"&gt;-w&lt;/span&gt;

&lt;span class="c"&gt;# Add a devDependency to all projects in the workspace&lt;/span&gt;
pnpm add &amp;lt;package-name&amp;gt; &lt;span class="nt"&gt;-Dw&lt;/span&gt;

&lt;span class="c"&gt;# Add a dependency to a specific project&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;apps/app_1
pnpm add &amp;lt;package-name&amp;gt;

&lt;span class="c"&gt;# Add a devDependency to a specific project&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;apps/app_1
pnpm add &amp;lt;package-name&amp;gt; &lt;span class="nt"&gt;-D&lt;/span&gt;

&lt;span class="c"&gt;# Update all dependencies in the workspace to their latest versions&lt;/span&gt;
pnpm update &lt;span class="nt"&gt;--latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster update of dependencies for many projects at once&lt;/li&gt;
&lt;li&gt;Less juggling with versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires updating more projects at once if they are tied to the same dependency in the root directory. They can still be split but it's somehow contrary to the goal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add direct connection of dependencies
&lt;/h3&gt;

&lt;p&gt;The workspaces are allowing us to manage external and internal dependencies way more easily. But what if I told you that you can directly use your other projects without even building them, not even mentioning publishing? With correct typescript configuration, you can even have types and intellisense still working. That is what you can achieve by connecting other of your libraries (previously packages) in your &lt;code&gt;package.json&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@monorepo/app_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@monorepo/commons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workspace:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@monorepo/ui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workspace:*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the asterisks instead of version numbers. These mean that we are using whatever version is the newest. In our case, it's always the local one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always the current version (also while developing in branches)&lt;/li&gt;
&lt;li&gt;No need to publish libraries as packages and reinstall them&lt;/li&gt;
&lt;li&gt;No local linking required to try out new library versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After a merge, dependencies might differ, potentially causing problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This caveat can be mitigated with good test coverage and TypeScript running automatically through the complete codebase on the server before every build (which I highly recommend, regardless; ex. on the &lt;code&gt;dev&lt;/code&gt; branch). This can be easily automated with &lt;a href="https://typicode.github.io/husky/" rel="noopener noreferrer"&gt;husky&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to learn more about creation of monorepo with Vue, check out other tutorials in the series. If you prefer starting with a solid foundation - try this &lt;a href="https://github.com/Nagell/monorepo_template" rel="noopener noreferrer"&gt;template&lt;/a&gt;, where I’ve put all of this into practice.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>vue</category>
      <category>nx</category>
      <category>pnpm</category>
    </item>
  </channel>
</rss>
