Vite websockets error.

Hi,

So I’ve recently deployed a Laravel application that uses Vite, I’ve been trying to fix this issue for a few days to no avail. The error I’m receiving is “[vite] failed to connect to websocket.”, it only occurs when serving the site over HTTPS. I’ve tried a few solutions, namely configuring hot module reloading in the Vite config but ultimately I’m just at an impasse and wondering if anyone has encountered this issue when deploying a Laravel application and resolved it?

Vite docs have this section but it’s for development purposes so I suspect it’s not the solution I’m looking for. Thanks in advance for any help :slight_smile:

hmr-error

This question wasn’t exactly Fly related but something I expect people running Laravel 9.x applications using Vite will encounter so I’ll post the fix I did here:

  1. Check the APP_PORT of your Laravel container, mine is 3005:3000.
  2. Add the following server config to your vite.config.js:
export default defineConfig({
    server: {
        port: '3005'
    },
    plugins: [
        laravel({
            input: ["resources/css/app.css", "resources/js/app.js"],
            refresh: true,
        }),
    ],
});

Hope that helps someone who ran into the same issue!

Hi!

I’m curious if there’s something to be adjusted on our end to make this better -

but I’m a bit confused, is that for running “npm run dev” (which starts the Vite development server?)

  1. Check the APP_PORT of your Laravel container, mine is 3005:3000.

This sounds like local development with Laravel Sail perhaps?

Hallo,

Honestly it confused me too as I didn’t expect HMR to be running in production. My generated docker file runs npm run build on deployment so I would expect it just to be using those assets and not worrying about a local development server. Even when disabling HMR I still ran into this issue, though I’m unsure if Laravel’s Vite plugin were somehow overriding this.

You’re right that I’m using Laravel Sail, I don’t run into an issue with this when developing locally as I’m not trying to serve over HTTPS, though I suspect if I did then I’d encounter the same issue. There’s definitely something going on beyond my knowledge as I’m not exactly sure why this fix even works, just that it does.

It’s also an issue I’ve seen others run into, there was actually a bug related to this that you can see more about in this thread with a fix here.

Thanks, that’s useful/good to know! I’ll check to see. I mostly test with fresh Laravel installations, I’ll try it again.

Would you mind pasting in your Dockerfile? (is it a stock dockerfile without changes?)

Yeah I haven’t touched it, here it is:

# syntax = docker/dockerfile:experimental

# Default to PHP 8.1, but we attempt to match
# the PHP version from the user (wherever `flyctl launch` is run)
# Valid version values are PHP 7.4+
ARG PHP_VERSION=8.1
ARG NODE_VERSION=14
FROM serversideup/php:${PHP_VERSION}-fpm-nginx-v1.5.0 as base

# PHP_VERSION needs to be repeated here
# See https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
ARG PHP_VERSION

LABEL fly_launch_runtime="laravel"

RUN apt-get update && apt-get install -y \
    git curl zip unzip rsync ca-certificates vim htop cron \
    php${PHP_VERSION}-pgsql php${PHP_VERSION}-bcmath \
    php${PHP_VERSION}-swoole php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /var/www/html
# copy application code, skipping files based on .dockerignore
COPY . /var/www/html

RUN composer install --optimize-autoloader --no-dev \
    && mkdir -p storage/logs \
    && php artisan optimize:clear \
    && chown -R webuser:webgroup /var/www/html \
    && sed -i 's/protected \$proxies/protected \$proxies = "*"/g' app/Http/Middleware/TrustProxies.php \
    && echo "MAILTO=\"\"\n* * * * * webuser /usr/bin/php /var/www/html/artisan schedule:run" > /etc/cron.d/laravel \
    && rm -rf /etc/cont-init.d/* \
    && cp .fly/nginx-websockets.conf /etc/nginx/conf.d/websockets.conf \
    && cp .fly/entrypoint.sh /entrypoint \
    && chmod +x /entrypoint

# If we're using Octane...
RUN if grep -Fq "laravel/octane" /var/www/html/composer.json; then \
        rm -rf /etc/services.d/php-fpm; \
        if grep -Fq "spiral/roadrunner" /var/www/html/composer.json; then \
            mv .fly/octane-rr /etc/services.d/octane; \
            if [ -f ./vendor/bin/rr ]; then ./vendor/bin/rr get-binary; fi; \
            rm -f .rr.yaml; \
        else \
            mv .fly/octane-swoole /etc/services.d/octane; \
        fi; \
        cp .fly/nginx-default-swoole /etc/nginx/sites-available/default; \
    else \
        cp .fly/nginx-default /etc/nginx/sites-available/default; \
    fi

# Multi-stage build: Build static assets
# This allows us to not include Node within the final container
FROM node:${NODE_VERSION} as node_modules_go_brrr

RUN mkdir /app

RUN mkdir -p  /app
WORKDIR /app
COPY . .
COPY --from=base /var/www/html/vendor /app/vendor

# Use yarn or npm depending on what type of
# lock file we might find. Defaults to
# NPM if no lock file is found.
# Note: We run "production" for Mix and "build" for Vite
RUN if [ -f "vite.config.js" ]; then \
        ASSET_CMD="build"; \
    else \
        ASSET_CMD="production"; \
    fi; \
    if [ -f "yarn.lock" ]; then \
        yarn install --frozen-lockfile; \
        yarn $ASSET_CMD; \
    elif [ -f "package-lock.json" ]; then \
        npm ci --no-audit; \
        npm run $ASSET_CMD; \
    else \
        npm install; \
        npm run $ASSET_CMD; \
    fi;

# From our base container created above, we
# create our final image, adding in static
# assets that we generated above
FROM base

# Packages like Laravel Nova may have added assets to the public directory
# or maybe some custom assets were added manually! Either way, we merge
# in the assets we generated above rather than overwrite them
COPY --from=node_modules_go_brrr /app/public /var/www/html/public-npm
RUN rsync -ar /var/www/html/public-npm/ /var/www/html/public/ \
    && rm -rf /var/www/html/public-npm \
    && chown -R webuser:webgroup /var/www/html/public

EXPOSE 8080

ENTRYPOINT ["/entrypoint"]

Hi @Cuji !

Can you check if your project has vite’s generated public/hot file in its directory? If the file exist, it will get deployed along with your other files!

Make sure to either delete the file before deployment, or include the path to it in .dockerignore!
Then try redeploying your app without your previous fix and see if that fixes the local websocket connection issue.

1 Like

@kathrynannetan you know you’re absolutely right, I’m so used to deploying from repositories rather than a working directory that it didn’t occur to me files ignored in source control are obviously still getting deployed.

So whilst the local development server is active and the temporary hot file exists under “/public/hot” it’s getting deployed and attempting to run a local development server in the deployed app, but of course, it’s not configured to be served over HTTPS so it’s returning an error as the WSS protocol requires to be served over HTTPS and I suppose there’s no valid cert it’s aware of.

The solution is just to add “/public/hot” to your .dockerignore file so it never it gets sent to the Docker daemon in the first place. Appreciate the help!

@fideloper-fly perhaps it’s worth noting down in the Laravel deployment docs somewhere, I imagine it’ll trip up a few others in the future - even if it is an edge case :slight_smile:

1 Like

Glad to hear it worked! @fideloper already made some changes to the Fly Laravel Launcher to exclude the file, hopefully we get the changes soon!

1 Like

That just got merged, should get into our next release of flyctl soon!

2 Likes

@fideloper-fly This made me think about some other areas this causes issues: if you’re using local / public filesystem disks then your local will always overwrite your deployed app unless otherwise specified in the .dockerignore. I was already experiencing this problem not realizing my “storage/app/public” folder was just getting overwritten.

I’m not totally sure I understand that one - let me say this, as it might clear it up:

If you need files (saved to filesystem storage directories) to persist across deploys, then you need to use a volume.

Each deploy will otherwise be a totally different set of files (whatever files are around when you run “fly deploy”)

Does that make sense? It’s possible you meant something else and I didn’t understand!

1 Like

:man_facepalming: don’t mind the following one, having a brain fart there!