# Deploy a Laravel and Vue app

> The production build Laravel Cloud documents, the Inertia SSR bundle, the queue, and the public/hot file that breaks a live site.

- URL: https://laravel-vuejs.space/deploy-laravel-vue
- Author: Anass Ez-zouaine (https://laravel-vuejs.space/author/anass-ez-zouaine)
- Published: 2026-10-10
- Topics: Laravel, VueJs, Tooling

Your app works locally, you deploy, and every page is a blank screen trying to load JavaScript from `localhost`. That’s one stray file. Here is the whole deploy for a Laravel + Vue app, in order, with the traps marked.

> **TL;DR**
>
> -   Build: `composer install --no-dev`, `npm ci`, `npm run build` (or `build:ssr`), `php artisan optimize`.
> -   Never deploy `public/hot`. Set every `VITE_*` before the build.
> -   `php artisan migrate --force`; skip `storage:link` on Laravel Cloud.
> -   Production needs a supervised queue worker (and an SSR process if you use SSR).
>
> **What you'll learn**
>
> -   The build commands for every release
> -   What must never reach the server
> -   How SSR, the queue and migrations fit in
>
> **Who it's for:** Developers shipping a Laravel + Vue starter kit app
>
> **Applies to:** Laravel 13 · Vue starter kit · Vite · Laravel Cloud · VPS

A Laravel and Vue app is one PHP app plus a Vite build. The mistake that takes sites down is shipping `public/hot`, which tells Laravel to load assets from the dev server. The rest is a short list.

This assumes the [Vue starter kit](https://laravel-vuejs.space/application-setup). A separate SPA from the [Sanctum guide](https://laravel-vuejs.space/laravel-sanctum-vue-spa) is two deploys: this one for the API, and the SPA host for the static files.

1. **Build** PHP and the assets.
2. **Cache** config, routes and views with `optimize`.
3. **Migrate** with `--force`.
4. **Keep** `public/hot` off the server.
5. **Run** the queue (and SSR) as long-lived processes.

## The build

[Laravel Cloud’s deploy guide](https://laravel.com/cloud/docs/deploy-guides/laravel) uses this on every release:

```bash
composer install --no-dev
npm ci --audit false
npm run build
php artisan optimize
```

`npm run build` writes `public/build` and a manifest. Blade’s `@vite` reads that manifest. `php artisan optimize` caches config, routes, and views into the release. Run it during the build, not after the release is already taking traffic.

> **Gotcha: optimize freezes the environment**
>
> `APP_ENV` must be `production` and `APP_DEBUG` must be `false` before `optimize`. A cached config file keeps whatever was in the environment at that moment. Change a key later and you need another `php artisan optimize`, or `php artisan optimize:clear` if you are debugging.

Then:

```bash
php artisan migrate --force
```

> **Heads up: storage:link on Cloud vs a VPS**
>
> `--force` is required when `APP_ENV` is production. Do not add `storage:link` to a Cloud deploy. Cloud’s disk resets on every deploy, so the symlink does not survive, and Cloud’s own guide says to skip that command. User uploads on Cloud belong in the object storage bucket Cloud attaches. On a VPS, `storage:link` is once per machine when that machine serves the `public` disk from the [file upload guide](https://laravel-vuejs.space/laravel-vue-file-upload).

## Do not deploy the dev server

> **Gotcha: public/hot**
>
> Vite writes `public/hot` while `npm run dev` is running. If that file is on the server, every page tries to load JavaScript from your laptop’s URL. Delete it. Do not commit it. The same goes for `node_modules`: install them in the build, then you only need the compiled files at runtime.

> **A pre-deploy check**
>
> ```bash
> test -f public/hot && echo "remove public/hot" || echo "ok: no public/hot"
> test -f public/build/manifest.json && echo "ok: manifest built"
> ```

Set every `VITE_*` variable before `npm run build`. Vite inlines them. A Reverb key added after the build does not show up until you build again. See the [Reverb guide](https://laravel-vuejs.space/laravel-reverb-vue).

## Server-side rendering

The Vue starter kit can render the first HTML on the server. Laravel Cloud says to swap the asset command when you want that:

```bash
npm run build:ssr
```

Use it instead of `npm run build` when those pages need server HTML. `npm run build` writes only the browser bundle and leaves `bootstrap/ssr` stale. Locally, `composer run dev` already server-renders. The current starter kit has no `dev:ssr` script. In production the SSR process has to stay up, the same way a queue worker stays up. Laravel Cloud and Forge both know how to keep that process running. On a VPS you supervise `php artisan inertia:start-ssr` yourself.

SSR is how a public Inertia page has HTML for a crawler before Vue boots. A logged-in app can ship the client bundle only. Pick that in [the stack guide](https://laravel-vuejs.space/laravel-vue-in-2026), not at the end of a deploy.

## The queue

Mail, broadcasts, and any `ShouldQueue` listener do nothing without a worker:

```bash
php artisan queue:work
```

`composer run dev` started one on your machine. Production does not.

|  | Laravel Cloud | VPS |
| --- | --- | --- |
| Queue worker | Managed, restarted on deploy | Supervise `queue:work`; run `queue:restart` after deploy |
| SSR process | Managed | Supervise `php artisan inertia:start-ssr` |
| `storage:link` | Skip (disk resets) | Once per machine |

On a VPS, supervise `php artisan queue:work` and signal it with `queue:restart` after each deploy so it loads the new code. Cloud restarts its workers itself, so `queue:restart` is not a Cloud deploy command.

If you broadcast with Reverb, that is a second long-running process. The asset build does not include it.

## A VPS in one paragraph

Point the web server at the `public` directory. The front controller is `public/index.php`. PHP 8.3 or newer, the extensions in [the requirements](https://laravel-vuejs.space/requirements), and a database. Put `public/build` on disk, keep `public/hot` off disk, run migrations, and supervise the queue. That is the same app Cloud builds. The host is the part you are now operating yourself.

## Recap

- One PHP app plus one Vite build.
- `optimize` after the environment is final.
- No `public/hot`, no `node_modules` at runtime.
- Queue and SSR are processes you keep running.

## FAQ

**Q: Why does my deployed Laravel site load assets from localhost?**

A `public/hot` file was deployed. Delete it and keep it out of git.

**Q: Do I need to run php artisan optimize on every deploy?**

Yes, during the build, after `APP_ENV` and `APP_DEBUG` are final. Re-run it after changing environment keys.

**Q: Should I run storage:link on Laravel Cloud?**

No. Cloud’s disk resets on every deploy; use the attached object storage bucket for uploads.

**Q: Why does my SSR HTML stay old after deploy?**

`npm run build` does not refresh `bootstrap/ssr`. Use `npm run build:ssr` and restart the SSR process.

## Next steps

- [Laravel Vite and Vue](https://laravel-vuejs.space/laravel-vite-vue)
- [Server-side rendering with Inertia](https://laravel-vuejs.space/inertia-ssr)
- [Laravel Reverb with Vue](https://laravel-vuejs.space/laravel-reverb-vue)

## Sources

- [Laravel Cloud: Deploying Laravel](https://laravel.com/cloud/docs/deploy-guides/laravel)
- [Laravel 13: Deployment](https://laravel.com/docs/13.x/deployment)
- [Laravel 13: Queues, Supervisor configuration](https://laravel.com/docs/13.x/queues#supervisor-configuration)
