Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mattbartel's avatar

SPA/PWA on Laravel Vapor

I'm working on a Laravel/Vue-powered Progressive Web App and hope to deploy it in Vapor. I've run into some issues with the way assets are handled in Vapor and wondered if anyone could offer suggestions.

My JavaScript/CSS assets are being built in Laravel Mix. As long as I use the correct asset() helper in my blade templates, plus extract my Vue styles into a separate file ("extractVueStyles = true" in Mix), the assets are correctly uploaded to the CDN and my code references are correctly updated.

My PWA service-provider is being built using the workbox-webpack-plugin. The last issue I'm having is how to serve the service-provider.js. My understanding is that service providers must be served from the same origin as the referrer or the browser will not register them, so the Vapor CDN doesn't work.

My only thought right now is to set up a laravel route that serves up the static service-provider.js file on the same domain, but obviously having the PHP overhead just to serve a static file is a little annoying.

Any ideas?

0 likes
11 replies
mattbartel's avatar

@mbryne I saw in a few places (can't find the links right now) that the service worker spec might not like redirects to other domains so I ended up proxying the assets through my app.

My steps:

  1. Configure Mix/webpack to generate the service worker assets as normal (I had the service worker in my public root and the other js assets in a dist/ directory).
  2. Create routes in Laravel that match the service worker files (you may need to use regular expressions for this because the asset file names will change when the service worker is regenerated). e.g.
Route::get('/service-worker.js', 'SpaController@proxyAsset');
Route::get('/dist/{asset}', 'SpaController@proxyAsset')->where('asset', '^workbox.*$');
Route::get('/dist/{asset}', 'SpaController@proxyAsset')->where('asset', '^precache.*$');
  1. My proxyAsset() method does an http request to get the contents of the files from the CDN, then caches those (I'm using redis/elasticache) until the next deployment.

I'm still not 100% sure this proxying is necessary and there's obviously a slight performance hit having to serve static content via PHP, but it shouldn't be too bad given that the client will cache all the assets after the first request (assuming you send the correct cache headers with the response).

Hope that helps!

mbryne's avatar

Thanks for getting back to me on that Matt,

I'm looking at putting together a Gatsby frontend sitting on top of a Laravel API, a lot of our content is static and would benefit from being offloaded to the CDN but I'll see how it all pans out,

Cheers!

Stanlley's avatar

I’ll show you a few code examples of how I did this. Hopefully this inspires more people to use both Vue and Laravel to create awesome applications for large industries https://www.upsers.online/

1 like
mattbartel's avatar

@mbryne Yeah, I was originally doing something similar with Vue CLI / Laravel API, but I ended up having to use Mix to get the Vapor to correctly upload the static assets to the CDN.

Finally in the end, I switched away from Vapor for deployment of the frontend and just used it for the API, deploying the frontend separately. Taylor's new Airlock package looks promising for simple SPAs. https://laravel.com/docs/master/airlock

mbryne's avatar

Thanks @mattbartel, it's looking like we will be doing the same on this end,

@stanlley could you share a few examples of how you have solved this issue?

mbryne's avatar

Hey @mattbartel what process did you end up using to deploy the frontend after all that? and did you end up using for Sanctum for authentication?

I really like the approach taking by this repo https://github.com/georgewritescode/Laravel-React-Typescript-Boilerplate/blob/master/resources/views/layouts/master.blade.php where the master layout is responsible for dishing out the initial CSRF token and authentication is handled via the default laravel routes and views. I would just prefer that than the JWT disparate frontend approach for me personally.

I've just found myself running into the same difficulties with deploying a CRA developed React app, doing everything I can to avoid ejecting because I really want to deploy the whole application via Vapor but still retain the capacity to receive updates from the frontend vendor we are using.

I'm happy to do that but I would love to know the general approach you or anyone else ended up taking for deployment and authentication of a standard index.html based SPA offloaded to Cloudfront via Vapor.

Cheers, Michael

mattbartel's avatar

Hey @mbryne,

I was in the same boat as you're in with CRA but with Vue CLI. Unfortunately, I ended up bailing on Vue CLI and using Mix. I injected initial state on first load with Blade, then ran a SPA from then on. CSRF headers are passed with every response so as long as your JS knows to look for them, that should work. I handle authentication using the normal session driver.

I never got to the point where I found out if the PWA service provider, etc. would be properly served by Vapor. As far as I could tell, the only way to do that is to serve it at the exact same root domain. So using Vapor, it would have to be generated using the Laravel routes (probably by proxying the CDN files, then caching in-app).

Given the current Vapor CDN setup, if I were doing it again now, I'd probably use Vapor to power the API only at api.whatever.com, then make a separate frontend deployment to a CDN somewhere, using Sanctum to handle the API auth.

Good luck. Let me know what you come up with!

mbryne's avatar

Thanks for getting back to me on that @mattbartel, that is what I have ended up doing on my end as well,

I was using create-react-app and wanted to avoid ejecting so we could continue to receive vendor updates, I ended up storing my SPA frontend in it's own subdirectory as part of my main repository.

An early step in the vapor build process generated a .env file in my SPA frontend which populated create-react-app's PUBLIC_URL with the automatically generated ASSET_URL variable generated by vapor deploy. This ensured create-react-app was able to do code-splitting and all other such niceties without too much fuss.

A later step in the build process actually stripped out all of the and tags from my generated create-react-app production build and saved them in an spa.blade.php file which was included alongside my usual initial main app layout, including the initial page state loading and CSRF token you mentioned.

The solution works and I'm comfortable with it because in the end it was easier to just wrangle the generated create-react-app HTML and build steps than eject and lose those upstream fixes and CRA scripts.

I also have not gotten to the stage where I have had to worry about the Service Worker and I don't envisage I'll look into that anytime soon either.

I think a vendor-supported vapor-esque solution for serving up static files with appropriate cache headers etc from the root domain would mitigate a lot of these issues and others (authentication domains with google analytics etc etc) but for now I'm happy with what I came up with.

If anyone needs a hand getting a create-react-app project integrated with a Vapor deployment process please don't hesitate to get in touch.

Thanks for your help again @mattbartel,

Cheers, Michael

Please or to participate in this conversation.