4

I am currently developing multiple Vue.js SPAs locally and have hit various problems when trying to serve these apps behind an NGINX reverse proxy. Specifically, I'm getting many errors related to either websockets, or sockjs-node, which kills the Hot-Reload functionality for the vue apps. (404 Not Found)'s.

Each of these SPAs are running on their own bare-bones Node.js server which dish out the static files on the root of their server. They should be accessed via the NGINX reverse proxy:

http://localhost:6200/ -> http://localhost/my-app1/
http://localhost:6205/ -> http://localhost/my-app2/
http://localhost:6210/ -> http://localhost/my-app3/

For development, I run all of the vue.js apps using npm run serve and then access those sites though the NGINX proxy: http://localhost/my-appX/. The sites loads without any functional issues, however hot reloading does not work.

After Looking at the developer console in the browser, there are numerous GET requests to EG: http://localhost/sockjs-node/info?t=1605955915882. This URL is obviously wrong, as we are currently accessing the site through the proxy - I would have expected to see http://localhost/my-appX/sockjs-node/info?t=1605955915882. Below is my current configurations prior to playing with various things in attempt to get it to work:

My NGINX configuration:

upstream nodefrontend_myapp1 {
    server localhost:6200;
}
upstream nodefrontend_myapp2 {
    server localhost:6205;
}
upstream nodefrontend_myapp3 {
    server localhost:6210;
}

# Redirect HTTP to HTTPS
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    
    # 307 temporary redirect
    return 307 https://$host$request_uri; 
}

# HTTPS Proxy
server {
    listen 443 ssl http2;
    server_name localhost;
  
    # Proxy "my-app" URI traffic to correct server
    location /my-app1/ {
        proxy_buffering off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # For websockets
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

        # Proxy to my-app's root
        proxy_pass http://nodefrontend_myapp1/;
    }

    # Note: several other sites are running via the below routes
    location /my-app2/ {
      proxy_pass http://nodefrontend_myapp2/;
      # ...
    }

    location /my-app3/ {
      proxy_pass http://nodefrontend_myapp3/;
      # ...
    }
}

The vue.config.js configuration:

module.exports = {
    // options...

    // I want the site to be accessible *RELATIVE* to the current URL.
    // This will make vue.js resources have no leading slash, eg: <link> href="css/file123.css" </link>
    // And should allow me to fetch resources on the current VHOST: eg: http://localhost/my-app/css/file123.css
    publicPath: "",
    
    devServer: {
        //disableHostCheck: true,
        https: false,
        public: "0.0.0.0",
        host: "localhost" ,
        port: 6200,
        sockPath: "", /////////// This has been causing me issues

    },

}

So after looking at the webpack docs, it seemed like I should updated the sockPath in each apps vue.config.js, so I did:

sockPath: "/my-appX/sockjs-node",

This stopped the 404 GET errors with sockjs-node, but then different errors appeared regarding websockets now not able to find its resources at: ws://localhost/my-appX/sockjs-node/147/0dj5ee1p/websocket.

To check if the issue was the webserver or NGINX, I ran wscat -c ws://localhost:6200/my-appX/sockjs-node/147/0dj5ee1p/websocket which DID work. This indicated NGINX was somehow killing the websocket. To double check, I tested the websocket through NGINX: wscat -c ws://localhost/my-appX/sockjs-node/147/0dj5ee1p/websocket, which DID NOT WORK.

After hours of playing with the configurations and NGINX, I found an article online saying that I should change my NGINX proxy_pass URL to remove the trailing slash. I changed it from this:

proxy_pass http://nodefrontend_myappX/;

to this:

proxy_pass http://nodefrontend_myappX;

And retested the websocket via wscat -c ws://localhost/my-appX/sockjs-node/147/0dj5ee1p/websocket and it worked!. However.. After removing that leading slash, Now the website doesnt load. My suspisions are the vue.config.js publicPath, but I'm at a loss.

Has anyone been able to run multiple Vue.js sites behind an NGINX reverse proxy (as VHOSTS), and have the hot reload functionality working???

====== I did find a solution, but I dont like it:

  • Host the SPA statically on its server under a sub-route: http://localhost:6200/my-app
  • Make the NGINX reverse proxy_pass goto: proxy_pass http://localhost:6200/my-app/
  • Set the vue.config.js file's publicPath to: publicPath: "/my-app"
  • Do NOT set the vue.config.js's sockPath property (Delete it).

The key to this configuration is that the vue.js front-end browser knows its located at /my-app, can send requests to the server hosted at /my-app, and NGINX does not really modify any URIs.

Although this configuration works, I dont like that the webserver MUST host the static files on a sub-route and the browser MUST know its vhost (publicPath) - Unless this is standard practice?? I am semi-okay with the browser knowing its publicPath (/my-appX/) as it needs it for history mode.

PS. In production, my origional configuration with a relative publicPath works fine. Its just HRM which breaks in development.

Anyone able to shed some light on the issue? Cheers

========= UPDATE 1) found fix, not sure if this is the only fix.

I found out my vue.config.js, devserver "public" property was set incorrectly. According to the webpack devserver documentation, the devserver client ( / sockjs-node) code running in the browser will guess the servers location by looking at its window.location variable. However, since I'm accessing the server via a proxy, it looks like it cant handle that by default. SO their solution is to set the "public" property to make the client talk DIRECTLY to the server (cross origin requests)... A bit of a hack if you ask me.

So the client sockjs-node now talks directly from: http://localhost/my-appX/ to http://lcoalhost:62xx/.

This is what (eg) my-app1's vue.config.js now looks like:

module.exports = {
    // options...

    publicPath: "",
    
    devServer: {
        //disableHostCheck: true,
        https: false,
        public: "localhost:6200",  //// This line makes the client browser talk DIRECTLY to this address / server for hot reloading.
        host: "0.0.0.0" ,
        port: 6200,

    },

}

With this said, Everything now works as expected via http. But updating NGINX to upgrade requests to https, causes the webpack-devserver sockjs-node to spit out CORS errors. This is because the client/browser is upgraded to HTTPS, but the webpack-devserver is only http. However to fix this, I simply made the vue.config.js devserver https, and the CORS errors disappear.

This seems more like an issue / limitation with the webpack-devserver sockjs-node than a stackoverflow issue. Will leave the issue open here.

1
  • Generally well-designed app should allow its usage under any prefix that can be specified under the app settings. However since your Vue apps are SPA, there can be some kind of a workaround. Can you inspect one of those failed requests and tell me, does that request have a Referer HTTP request header? Commented Nov 25, 2020 at 14:08

1 Answer 1

3

If you want to keep the hot reload (not sure if that is what you want) you have to add a location like this one

location /sockjs-node {
    proxy_pass http://nodefrontend_myapp;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}
Sign up to request clarification or add additional context in comments.

5 Comments

Ah sorry - edited original NGINX config* But yes I'd like to keep the hot reload in tact. However, your solution would be fine IF I had a single reverse proxy. (Correct me if I'm wrong) - I have many reverse proxies, each forwarding to a vue.js server (see updated NGINX). This would mean each site (in the browser) would send: localhost/sockjs-node, and NGINX would not know which server to route it to, correct?
See edited answer, proxy to the upstream then.. this solution works 100%.
Yep I agree this will work 100% which I've tested, but this isn't directly the source of my issue. If I had a single SPA server(hosts index.html on root), and a single reverse proxy(routes upstream to servers root), this NGINX configuration would work great. The problem is the VHOST part - where each server is on its own route, IE: location /my-app1/ { proxy_pass frontend_myapp1; ... } location /my-app2/ { proxy_pass frontend_myapp2; ... } Simply prefixing the servers routes with /my-app1/ or /my-app2/ causes chaos with the hot reload.
Have you tried omitting the proxy_pass line and removing the sockPaths from the vue.configs so it works for whichever app has made changes.
Yep I gave that also a go, but didn't yield any results. PS, I think i found the source of my issue - see my update. But I think this is a limitaition with sockjs-node / webpack-devserver.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.