Part 3 of 3
For several development sessions, a frontend application (Vite + React) exhibited a catastrophic "Silent Hang" upon startup. The development server would launch successfully—listing ports and network addresses—but the browser would hang indefinitely on a loading state. No error messages appeared in the browser console, the network tab, or the terminal.
The root cause was a filesystem recursion deadlock caused by an overly permissive glob pattern in tailwind.config.js. This configuration inadvertently forced the Tailwind CSS Just-In-Time (JIT) compiler to scan the entire node_modules directory—tens of thousands of files—on every request, blocking the Node.js event loop and preventing any HTTP response.
This postmortem details the symptoms, investigation process, root cause analysis, and definitive fix.
The behavior was specifically maddening because it mimicked a network or proxy failure rather than a build error.
http://localhost:5173./) stalled in a Pending state for minutes, eventually timing out.curl -v http://localhost:5173 completed the TCP handshake but never received a single byte of data. Time to First Byte: effectively infinite.This led to several red herrings:
/api) misconfigured or deadlocking?All reasonable hypotheses. All wrong.
We employed systematic isolation to find the failure.
App.jsx with a static "Hello World" component.This was the smoking gun. A minimal React component worked fine, but importing a CSS file killed the server. The culprit had to be in the CSS processing pipeline: PostCSS and Tailwind CSS.
Re-examining the terminal logs revealed warnings that had been overlooked in the noise:
warn - The `content` option in your Tailwind CSS configuration is missing or empty.
warn - No utility classes were detected in your source files.The tailwind.config.js file contained what appeared to be a standard configuration:
export default {
content: [
"./index.html",
"./src/**/*.{js,jsx}",
],
// ...
};Tailwind CSS's JIT compiler scans source files for utility class patterns (like text-red-500, flex, p-4) to generate only the CSS you actually use. It relies on glob patterns to find these files.
In this specific environment—a monorepo with symlinked dependencies—the glob resolution was traversing symbolic links that led back into node_modules. The node_modules directory typically contains 50,000+ files across thousands of packages.
When the browser requested the page, Vite triggered the CSS transformation pipeline. Tailwind's file scanner then attempted to stat() and read() every file it could reach through the symlink maze. These operations, while individually fast, created a cumulative I/O burden that overwhelmed the system.
The Critical Detail: Node.js runs JavaScript on a single-threaded event loop. While modern Tailwind uses asynchronous file operations, the sheer volume of files—combined with synchronous glob pattern matching—blocked the event loop long enough that HTTP requests queued indefinitely. The server was alive but unreachable: The Silent Hang.
This bug required a specific combination of factors:
node_modules exclusionModern Tailwind CSS (v3.3+, released March 2023) automatically excludes node_modules from content scanning. This bug primarily affects older projects or those with non-standard configurations.
The solution is to explicitly exclude node_modules from the content scan:
// tailwind.config.js
export default {
content: [
"./index.html",
"./src/**/*.{js,jsx}",
"!**/node_modules/**" // Explicit exclusion
],
// ...
};The ! prefix in a glob pattern is a negation. By adding !**/node_modules/**, we instruct the glob engine: "Under no circumstances should you descend into any directory named node_modules, anywhere in the tree."
This single line eliminates the recursive traversal entirely.
node_modules exclusion is automaticfollowSymbolicLinks: false)After applying the fix:
Silent failures are the worst failures. A crash with a stack trace is a gift. A hang with no output is a puzzle.
"It works on my machine" has layers. Monorepo symlinks, OS-specific glob behavior, and dependency version differences can create bugs that only manifest in specific environments.
Check your tools' configuration first. Before debugging network proxies, backend services, or port conflicts, verify that your build tools aren't doing something pathological.
Warnings are errors you haven't hit yet. Those Tailwind warnings in the terminal were the answer all along—buried in startup noise.
If a Vite/Tailwind project starts but refuses to serve requests:
tailwind.config.js content patternsnode_modules is excluded from glob patternsThis postmortem documents an actual debugging session. The specific project details have been generalized, but the technical analysis and fix are directly applicable to any Vite + Tailwind CSS project experiencing similar symptoms.
Curated By: Tom Hundley
Written By: Gemini 2.5
Fact-Checked & Edited By: Claude Opus 4.5
Part 3 of 3
Discover more content: