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

twoarmtom's avatar

Using ServiceWorkers with Laravel Vapor

I use the package silviolleite/laravelpwa to load my project as a progressive web app (mostly for appearance on mobile when saved to the Homescreen). I'm trying to move my project to Laravel Vapor, and the during deployment the serviceworker.js is moved from the public folder to the assets folder in AWS S3 (automatically for those not familiar with Vapor).

I've tried the following workarounds.

For reference, the package inserts the following into the page <head> tag:

<script type="text/javascript">
    // Initialize the service worker
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('/serviceworker.js', {
            scope: '.'
        }).then(function (registration) {
            // Registration was successful
            console.log('Laravel PWA: ServiceWorker registration successful with scope: ', registration.scope);
        }, function (err) {
            // registration failed :(
            console.log('Laravel PWA: ServiceWorker registration failed: ', err);
        });
    }
</script>

This is not allowed, because it is an external file:

<script type="text/javascript">
    // Initialize the service worker
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register({{ env('ASSET_URL') . '/servicesworkder.js' }}, {
            scope: '.'
        }).then(function (registration) {
            // Registration was successful
            console.log('Laravel PWA: ServiceWorker registration successful with scope: ', registration.scope);
        }, function (err) {
            // registration failed :(
            console.log('Laravel PWA: ServiceWorker registration failed: ', err);
        });
    }
</script>

And redirects are not allowed.

Route::redirect('/servicesworker.js', env('ASSET_URL') . '/serviceworker.js');

I've also tried to return the asset as a file and I get a FileNotFound error.

Route::get('/serviceworker.js', function() {
	return response()->file(env('ASSET_URL') . '/serviceworker.js');
});

// returns a message like the following
the file "https://...asset_url.../serviceworker.js" cannot be found.

Any suggestions on how I could get this to work?

0 likes
6 replies
twoarmtom's avatar

I realize now this has been discussed already and with much more technical attempts compared to mine. It seems it’s not really ideal to use Vapor if I’m set on keeping PWA functionality.

SPA/PWA on Vapor

twoarmtom's avatar
twoarmtom
OP
Best Answer
Level 5

After contacting Vapor support I got the following code for linking to the serviceworker.js asset and it works perfectly!

Route::get('/serviceworker.js', function () {
    return response(file_get_contents(asset('/serviceworker.js')), 200, [
    'Content-Type' => 'text/javascript',
    'Cache-Control' => 'public, max-age=3600',
    ]);
});
5 likes
stgbuc's avatar

@twoarmtom , it seems we are using the same package, how did you configure the config/laravelpwa.php path for icons/images? Currently I am hardcoding the path of the images with the one that is in the S3 Bucket, but this is not ideal .

Thanks

twoarmtom's avatar

@strategad at the time I posted this I ended up aborting using Vapor. However, a few months ago I moved all of my main projects to Vapor and stopped using this package. I was using it more for a 'web app' feel when saved to mobile desktop (not local data storage) so I opted for use head tags to allow opening with no address bar in the browser when saved to the home screen. Function was the same for my use case.

Froelund's avatar

For anyone ending up here, who is using vite - this is how we've solved it :)

Route::get('/serviceworker.js', function () {
    $serviceWorkerPath = Vite::asset('resources/js/service-worker.ts');

    $serviceWorkerContent = Http::withOptions([
        'verify' => ! app()->environment('local'),
    ])->get($serviceWorkerPath)->body();

    return response($serviceWorkerContent, 200, [
        'Content-Type' => 'text/javascript',
        'Cache-Control' => 'public, max-age=3600',
    ]);
});
1 like

Please or to participate in this conversation.