vite_ruby icon indicating copy to clipboard operation
vite_ruby copied to clipboard

Vite Rails seems to be significantly constrained by IO-bound proxying through the Rails server

Open joeldrapper opened this issue 4 months ago • 6 comments

In development mode, Vite can end up requesting hundreds of individual resources. This is by design and it’s a performance optimisation because each resource can be cached and reloaded efficiently.

However, this quickly clashes with the way that Vite Rails is set up by default for any reasonably large application.

We noticed that reloading our application in development took upwards of 10 seconds, and most of that time was spent waiting for Vite. The first load after a clobber took more like 30 seconds. However, none of the Ruby or Node processes seemed to use much CPU during this time.

We found that although Vite runs a fast asset server, Vite Rails was set up to proxy assets through the Rails server on /vite-dev/*. With one Puma worker configured with three threads, reloading would cause the requests to queue in Puma.

Reducing our Puma thread count to one demonstrates this problem further, increasing the time to reload to about 30 seconds.

https://github.com/user-attachments/assets/0a65248d-9325-4cba-a2e3-63e53920bb56

If we increase the Puma thread count to 100, a reload takes about one second.

https://github.com/user-attachments/assets/fce7f406-d173-404b-846e-f67d2b303e4c

Since we run our local development environment through Caddy, we were able to optimise this even further by configuring Caddy to proxy these requests directly to the Vite server.

Here’s an example configuration.

myapp.localhost {
  handle /vite-dev/* {
    reverse_proxy localhost:3036
  }
  reverse_proxy localhost:5400
  tls internal
}

I’m reporting this as a bug because it was very un-obvious why there was such a performance bottleneck and this is the default configuration if you follow the guides. I also expect there are many other apps set up like this taking tens of seconds to load. However I don’t know how it can be fixed.

One solution would be for the helpers to return external asset paths. This would allow Vite to handle the requests directly, but it would possibly introduce issues with content security policies, etc.

The other solution is to strongly encourage the use of a fast proxy like Caddy, or a local Puma configuration optimised for concurrency. This could be integrated in the documentation and install scripts somehow.

If you install Vite Rails in a new app, it’s unnoticeable at first because there are only a few assets. But every asset you add increases concurrent stress on Puma.

We spent ages trying to hunt down what we thought was a bad import causing Vite to hang but we found that every asset seemed to have an impact no matter how large it was. For example, deleting all our stimulus controllers knocked a few seconds off. Adding half of them back added half the time back. It was nothing to do with the assets themselves but the huge number of concurrent requests hitting Puma.

I don’t know if you have easy access to a Vite-Rails app with a lot of assets, but you can reproduce this with the open source RubyEvents app. It takes about 10 seconds to reload in development if you configure puma threads to one.

RubyEvents is just small enough that you don’t really notice it with the default configuration of three puma threads. But it is still impacted and it’s much faster with more threads.

joeldrapper avatar Aug 22 '25 11:08 joeldrapper

Even if the documentation and install scripts address this issue going forward, there are likely thousands of projects already significantly impacted and the documentation and install scripts can’t help with those, so I would love to find a better solution that can be pushed out as an update. 🧐

At least Puma is multi-threaded with typically 3-5 threads. Single-threaded servers like Unicorn and Pitchfork will be impacted even more.

joeldrapper avatar Aug 22 '25 11:08 joeldrapper

We are impacted by this exact issue when using Caddy for local tls. Our app will request > 1k js assets and proxying through rails causes it to take upwards of 15s.

jasonkoon avatar Aug 22 '25 14:08 jasonkoon

I can reproduce this with or without Caddy. Caddy just gives us an opportunity to fix it with native code, which is much faster than the Puma fix. If you run the RubyEvents app with bin/dev and puma configured to 1 thread, it takes about 10 seconds for each page reload.

joeldrapper avatar Aug 22 '25 14:08 joeldrapper

There's a built-in option skipProxy to skip the proxy, but it has some incompatibility issues that make it tricky to enable it out of the box without breaking existing projects.

I'd like to enable that by default in the next major.

By skipping the proxy and enabling HTTP2 (+TLS), even projects that make a large number of requests will load faster.

ElMassimo avatar Aug 22 '25 14:08 ElMassimo

That sounds good. Skipping the proxy by default will give an opportunity to explain the consequences of enabling the proxy and suggest either increasing puma thread count in dev or using a native proxy like Caddy.

I think skipping the proxy would also have a content security policy impact, if I’m not mistaken.

joeldrapper avatar Aug 22 '25 14:08 joeldrapper

I am user Docker Compose running Vite via a Procfile.dev in my container.

Is there a way to set the host used for tag helpers, that is different from the host passed to vite ? Or a way to allow binding to most hosts in vite?

henrikbjorn avatar Nov 27 '25 14:11 henrikbjorn