<?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: 99 tools</title>
    <description>The latest articles on DEV Community by 99 tools (@99_tools_dev).</description>
    <link>https://dev.to/99_tools_dev</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%2F4120997%2Fc07fc796-dd63-4f90-b46a-298bcfb152dd.png</url>
      <title>DEV Community: 99 tools</title>
      <link>https://dev.to/99_tools_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/99_tools_dev"/>
    <language>en</language>
    <item>
      <title>Two bugs that shipped 114 tools' JavaScript to every page</title>
      <dc:creator>99 tools</dc:creator>
      <pubDate>Fri, 11 Sep 2026 14:36:02 +0000</pubDate>
      <link>https://dev.to/99_tools_dev/two-bugs-that-shipped-114-tools-javascript-to-every-page-3ef5</link>
      <guid>https://dev.to/99_tools_dev/two-bugs-that-shipped-114-tools-javascript-to-every-page-3ef5</guid>
      <description>&lt;p&gt;I've been building a set of browser-based developer utilities — JSON formatting, diffing, hashing, image and PDF work, that sort of thing. 114 of them now, all on one Next.js static export.&lt;/p&gt;

&lt;p&gt;Two problems took far longer to understand than to fix. Both are the kind that don't announce themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Every page shipped every tool's code
&lt;/h2&gt;

&lt;p&gt;The site has a registry: one module listing every tool, its metadata, and its React component. The header imports it to build the search palette. That seemed harmless.&lt;/p&gt;

&lt;p&gt;It wasn't. Here's the rule I didn't know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the App Router, a server module that references a &lt;code&gt;'use client'&lt;/code&gt; module — statically, through &lt;code&gt;next/dynamic&lt;/code&gt;, or through a lazy &lt;code&gt;import()&lt;/code&gt; thunk — has that module's chunk attached to &lt;strong&gt;every route that imports it&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;next/dynamic&lt;/code&gt; felt like it should defer the cost. It doesn't. It defers &lt;em&gt;execution&lt;/em&gt;, not chunk attachment. And because the registry was imported by the layout's header, it was imported by every page — so every page carried every tool's JavaScript, including the PDF library, the OCR engine, and the image processing code.&lt;/p&gt;

&lt;p&gt;A visitor opening a JSON formatter was downloading a barcode scanner.&lt;/p&gt;

&lt;p&gt;The fix was a split by concern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the registry holds &lt;strong&gt;data only&lt;/strong&gt; — slugs, names, categories, keywords&lt;/li&gt;
&lt;li&gt;the UI map lives in its own client module&lt;/li&gt;
&lt;li&gt;the prose map is server-only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing clever, but you have to know the rule first. If you want to check your own: &lt;code&gt;ANALYZE=1 next build&lt;/code&gt; emits source maps you can inspect.&lt;/p&gt;

&lt;p&gt;The tell, before I understood the cause, was that every page had suspiciously similar JS weight regardless of what was on it. If your pages all cost the same, something shared is pulling everything in.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A 26.5 MB file I never load broke the entire deploy
&lt;/h2&gt;

&lt;p&gt;The deploy started failing with &lt;code&gt;Asset too large&lt;/code&gt;. The site is static files on Cloudflare, which has a &lt;strong&gt;25 MiB hard limit per asset&lt;/strong&gt; — and exceeding it fails the &lt;em&gt;whole&lt;/em&gt; deploy, not just the page that needs the file. Nothing else ships.&lt;/p&gt;

&lt;p&gt;The culprit was a WebAssembly build I never use.&lt;/p&gt;

&lt;p&gt;I use ONNX Runtime Web for one tool, pinned to the plain WASM backend. But the package also ships a JSEP build — the WebGPU/WebNN variant — and it contains its own &lt;code&gt;new URL(..., import.meta.url)&lt;/code&gt; reference. The bundler traced that reference and emitted a copy of the 26.5 MB file into the output.&lt;/p&gt;

&lt;p&gt;Nothing in my source mentions it. No page requests it. The runtime is explicitly pinned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wasmPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/wasm/ort/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;ort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executionProviders&lt;/span&gt; &lt;span class="o"&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;wasm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was emitted purely because a file inside a dependency referenced itself in a way the bundler follows.&lt;/p&gt;

&lt;p&gt;The fix was an ignore file listing that one asset so it isn't uploaded — kept deliberately narrow, because the &lt;em&gt;other&lt;/em&gt; ONNX wasm file in the same folder is the one the tool actually needs. A broad pattern there silently breaks the feature.&lt;/p&gt;

&lt;p&gt;Cloudflare also caps at 20,000 files. Worth knowing before you generate a page per item.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone starting this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bundlers follow references inside your dependencies, not just yours.&lt;/strong&gt; A file you never import can end up in your output because something in &lt;code&gt;node_modules&lt;/code&gt; pointed at itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static-host limits fail loudly but late.&lt;/strong&gt; Per-asset size and file count are worth checking on day one, not on deploy day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Lazy" in a bundler usually means deferred execution, not deferred download.&lt;/strong&gt; Verify which one you're getting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make the build check itself.&lt;/strong&gt; Mine now runs 3,877 assertions over the output directory before deploying, and refuses to ship if any fail. The tool logic has 3,175 tests of its own. Most of both exist because of a specific thing that broke once.&lt;/p&gt;




&lt;p&gt;The tools are at &lt;a href="https://99tools.dev" rel="noopener noreferrer"&gt;99tools.dev&lt;/a&gt; if they're useful to you. Everything runs in the browser — nothing you paste is uploaded, and they keep working with the network disconnected. Two exceptions, badged in the UI: the DNS lookup and the IP viewer genuinely need a server. Analytics is the cookieless kind, and there's nothing to sign up for.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about either bug.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>performance</category>
      <category>cloudflare</category>
    </item>
  </channel>
</rss>
