TypeScript

Chrome hangs with Vite dev server: raise the file limit

See why only Chrome stalls on a big Vite app while Firefox loads it, reproduce the stall with one changed limit, and make the fix survive a reboot.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

A React app migrated from webpack to Vite loads in Firefox, but in Chrome the tab freezes, and DevTools shows a long list of static files stuck in Pending. That is the report in the Stack Overflow question Chrome hangs with Vite Dev Server. There is no error message to search for, only requests that never finish.

TL;DR#

  • Cause: on Linux, Chrome runs out of open file descriptors. In development, Vite serves each source module as a separate request, so a large app asks the browser for thousands of files at once.
  • Fix (Ubuntu): add * - nofile 65536 to /etc/security/limits.conf, then reboot. On systemd distributions, set DefaultLimitNOFILE=65536 in /etc/systemd/system.conf and /etc/systemd/user.conf.
  • Running ulimit -Sn in a terminal only fixes a Chrome started from that terminal.
  • Using a self-signed HTTPS certificate? That is a second, separate cause: switch to a trusted certificate.

The symptom#

The asker describes a big React app with many dependencies and no lazy loading yet. In Chrome the tab hangs, and DevTools shows "many static files are pending and won't load". Firefox on the same machine loads the page. The asker's accepted answer adds that the same project "worked just fine without any additional configuration" on Windows.

Vite's troubleshooting guide has a heading for exactly this: Requests are stalled forever.

Why it happens#

The docs give the mechanism in one sentence:

As Vite does not bundle most of the files, browsers may request many files which require many file descriptors, going over the limit.

A webpack dev server sends the browser a few bundles. Vite's dev server sends native ES modules. The browser parses main.jsx, finds its imports, requests each one, then requests their imports, and so on through the whole module graph. The asker's app had no lazy loading, so every page component and its dependencies landed in the first load. That can be thousands of requests from a single tab, and each one in flight holds descriptors in the browser.

Two details from the Vite issue tracker explain the rest of the symptom:

  • The browser's limit is the one that runs out. vitejs/vite#11468 was opened by a SvelteKit user on macOS whose page hung in both Firefox and Chrome, with "no error or notification in the vite cli". That issue was eventually closed by PR #15395, an SSR fix for circular dependencies, so the original reporter's hang had a different cause. A fix posted in that thread by a later commenter on Ubuntu is the comment the accepted answer links to. That comment says that with "plenty of files Chrome demands more file descriptors than system allows". It also says that running ulimit -Sn "updates the amount of descriptors but that has to be done permanently as Chrome doesn't use it". A Chrome window opened from the desktop was started by your session manager, not by your terminal. It never inherits a limit you raise in a shell afterwards.
  • The reports fixed by raising the limit come from Chromium-based browsers on Linux. In the earlier vitejs/vite#5310 ("Forever-pending requests to dev server"), the reporter could reproduce it in Brave and Chromium but not in Firefox or GNOME Web. A contributor could not reproduce it in Chrome on macOS, and the reporter could not reproduce it in Chrome or Edge on Windows 10, although a few later comments report hangs on macOS and Windows too. The Vite maintainers answered with a troubleshooting page. The Ubuntu limits.conf line was added by PR #11959, titled "docs(troubleshooting): add network stalled solution for ubuntu linux (fixes #11468)" and merged on 14 February 2023. It is a documentation change, not a dev server change.

Neither the Vite docs nor these issues explain how Chrome manages descriptors internally, so this article does not guess. What can be shown is the cause and effect: one limit, changed on its own, decides whether the page loads.

Reproducing the stall with one changed limit#

This reproduction ran on 24 October 2026 in a Debian bookworm Node 22 Docker container. It used Vite 8.3.5 with its default config and Chromium 155 from Debian, running headless. The test app is a single main.js that imports 3000 tiny modules and then writes a marker into the page. Only the limit of the Chromium process changed between runs.

With the browser limited to 512 open files, the page never finished. After two minutes, timeout killed Chromium, the page still showed its initial text, and Chromium's own log reported running out of files:

text
=== RUN 1: dev server, browser fd limit 512
ulimit -Sn => 512
chromium exit code: 124   elapsed: 120 s
page marker: <div id="out">WAITING
'Too many open files' lines in chromium stderr: 5
 Creating shared memory in /tmp/.org.chromium.Chromium.JCIg0T failed: Too many open files (24)

The second run used the same server and the same page. The only change was the browser limit, now 65536:

text
=== RUN 2: same dev server, same page, browser fd limit 65536 (the only change)
ulimit -Sn => 65536
chromium exit code: 0   elapsed: 23 s
page marker: <div id="out">LOADED modules=3000
'Too many open files' lines in chromium stderr: 0

The negative control was the same app built with vite build and served by vite preview, with the browser back at the low limit of 512. The 3000 modules became one bundle, and the page loaded in four seconds:

text
=== RUN 3 (negative control): vite build + vite preview, browser fd limit 512
✓ 3004 modules transformed.
files in dist/assets: 1
ulimit -Sn => 512
chromium exit code: 0   elapsed: 4 s
page marker: <div id="out">LOADED modules=3000

In this setup the stall needed both conditions: an unbundled dev server, and a browser whose descriptor limit was too low for the module graph. The same bundle-versus-modules difference is consistent with the asker's observation that only the dev server is affected.

Fix#

1. Check the limit your browser actually has#

The number that matters is the one on the running Chrome process, not the one in your shell. Linux shows it in /proc/<pid>/limits (proc(5)):

bash
# Limit of your interactive shell (for comparison)
ulimit -Sn
 
# Limit of the oldest running Chrome process (use chromium for Chromium builds)
grep "Max open files" "/proc/$(pgrep -o chrome)/limits"

There is no single safe number: how many descriptors a page needs depends on the browser and the module graph. In the exploratory runs of the reproduction above, a 3000-module page also loaded with a browser limit of 1024, but not with 512. The reliable test is step 2: raise the limit and see whether the page loads.

2. Quick test: launch Chrome from a shell with a higher limit#

Quit Chrome completely first. Then, in one terminal:

bash
ulimit -Sn 10000   # you might need to change the hard limit too
google-chrome http://localhost:8080/

If the dev server now loads, the descriptor limit is confirmed as the cause. This only lasts as long as that terminal session.

3. Make it permanent on Ubuntu: limits.conf#

The Vite docs say that on Ubuntu you "may need to add the line * - nofile 65536 to the file /etc/security/limits.conf instead of updating systemd config files". The accepted answer used this fix:

bash
echo '* - nofile 65536' | sudo tee -a /etc/security/limits.conf

Then reboot. The docs are explicit that these settings persist but "a restart is required", and the accepted answer only saw the change after rebooting.

4. Or on systemd distributions: DefaultLimitNOFILE#

On Arch-based systems, the reporter of #5310 fixed it by adding this line, uncommented, to both /etc/systemd/system.conf and /etc/systemd/user.conf:

ini
DefaultLimitNOFILE=65536

Reboot after the change. The commenter in #11468 found that the same setting did not take effect on Ubuntu, which is why the docs point Ubuntu users to limits.conf.

5. Raise the inotify limits too#

The docs name inotify limits alongside file descriptor limits as a possible cause of stalled requests on Linux, and list these temporary changes:

bash
sudo sysctl fs.inotify.max_queued_events=16384
sudo sysctl fs.inotify.max_user_instances=8192
sudo sysctl fs.inotify.max_user_watches=524288

Verify the fix#

After the reboot, run the grep "Max open files" check from step 1 again. The browser's soft limit should now show 65536. If it still shows the old value, the setting did not reach your desktop session: see the DefaultLimitNOFILE step. Then open the dev server with DevTools on the Network tab and Disable cache ticked, and reload. The module requests should all complete, and the Pending column should drain to zero. If one specific file stays pending while the rest load, you are looking at a different problem, such as a plugin or proxy that never responds, not the descriptor limit.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

When the limit is already high: self-signed HTTPS and dev containers#

Two other documented causes produce the same "requests never finish" picture in Chrome.

A self-signed certificate. If the dev server runs on HTTPS with a certificate your machine does not trust, the Vite docs say: "When using a self-signed SSL certificate, Chrome ignores all caching directives and reloads the content. Vite relies on these caching directives." The fix is a trusted certificate. Raising limits will not help here.

A VS Code dev container. When Vite runs inside a dev container and you reach it through VS Code port forwarding, the request "may appear to be stalled". The docs explain that VS Code port forwarding does not support IPv6, and recommend setting server.host to 127.0.0.1. The asker's config already set host: "0.0.0.0" and did not use a dev container, so this was not their cause.

The accepted answer also mentions downgrading to Vite 3.2.5 as a last resort. The reproduction above stalled on Vite 8.3.5 and recovered with only the browser limit raised. A downgrade does not change that limit, so it is not the fix to try first. Lazy-loading routes with React.lazy reduces how many modules the first page load requests. That is worth doing for a large app, but it lowers the demand rather than raising the ceiling.

If a different error appears next#

Raising the inotify limits is also the fix when Vite itself crashes with Error: ENOSPC: System limit for number of file watchers reached. When the dev server runs in Docker on Windows and HMR stops reacting to your edits, the cause is file-change events that never cross from Windows into the container. React hot reload not working in a Docker container walks through the polling setting for Vite and react-scripts. If the dev server works but a deployed build fails in Chrome with Failed to fetch dynamically imported module, the cause is chunk names that changed between deploys, covered in Failed to fetch dynamically imported module. If Vite does not start at all and stops on Expected identifier but found "import" while loading its config, see the esbuild define error.

A Chrome tab that hangs on a Vite dev server is rarely a Vite bug. It is the browser running into an operating system limit that a bundled webpack setup never got close to. Check the limit on the running Chrome process, raise it in limits.conf or systemd, and reboot. The same page then loads in Chrome as it already did in Firefox.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote