Creating A Progressive Web App With Laravel Vapor

Sunglasses, Adult, Male, Man, Person, Head, Face, Art, Smoke, Cartoon, Graphics, Drawing
Headshot image of author

Oct 7th, 2022 By Taylor Perkins Full Stack Developer

Hey everyone! I was recently chatting with a friend and he showed me how to install his fasting app on my android. I had heard of Progressive Web App's in the past, but I never quite absorbed exactly what it was. It was early on in my career and such a complex subject went totally over my head. But now here I am, building out a side project I just picked up and thought it would be the perfect opportunity to nerd out and figure out how to do this. I didn't realize how simple it actually could be to create a PWA. Now, I do want to preface this article with the fact that I do not believe my PWA is truly a PWA. By that I mean, I have not yet implemented a service worker. I am unsure of the implications behind this, but that said, I was able to still create an installable PWA for Android at the least. Let's take a look at least at how to set this up without a service worker in Laravel Vapor.

Serving Static Assets In Laravel Vapor

It turns out that a simple manifest.json with just a few properties can achieve an installable PWA. Albeit, it may not be a wholly complete PWA. I want to reiterate that I am no expert and am simply sharing my experience of solving this problem myself. Vapor can sometimes be a little complex to serve static assets such as the manifest.json. Usually if you add something like a sitemap.xml to your public directory, Laravel Vapor will automagically do some things and upload the asset so it can be served through cloudfront. Let's take a quick look at how I implemented serving a static asset such as this blog's sitemap.xml. First thing's first, we simply just create a sitemap.xml in the public directory of Laravel. That's it really. You simply deploy to Laravel Vapor with this addition, and keep an eye out in the Vapor logs. You should see a line that says something like "Uploading Asset: sitemap.xml". Since it is Laravel Vapor though, you can't exactly just serve this up as if it was on your local dev env or a typical hosted application. We'll need to create a route to serve your sitemap. 

1Route::get('/sitemap.xml', function () {
2 return response()->redirectTo(asset('sitemap.xml'), 302, [
3 'Content-Type' => 'application/xml',
4 'Cache-Control' => 'public, max-age=3600',
5 ]);

This may not be ideal, but we have to work around some limitations with Laravel Vapor. We are simply defining the GET route to then redirect that request to where the actual sitemap is located at. If you were to visit your Vapor vanity url at /sitemap.xml at this point, you would probably notice that it redirected you to a cloudfront URL. Now let's take a look at your manifest.json.

Creating A Manifest For Your PWA In Laravel Vapor

We can apply some similar logic to serving up a manifest.json as well, but its not as simple as a sitemap for reasons I am unsure of yet. I definitely plan on updating everyone in the next PWA article I write with my final solution. For the time being, we have to do some manual intervention in order to get this to work. Let's first take a look at what the actual manifest will look like then we can dive into uploading and serving it. There is some great documentation on web app manifests which thoroughly go into detail how to create your manifest. As always, we are going to take an iterative approach to things. Let's start with the simplest manifest possible.  

2 "name": "AppNameHere",
3 "short_name": "AppNameHere",
4 "display": "standalone",
5 "scope": "/"

This will get you an "installable" PWA. It may not be pretty or anything since there's no icons defined, but you will be able to "add to home screen" at least on Chrome for Android (I have yet to test other browsers or operating systems). Next is the unfortunate manual intervention that I am going to solve. We have to take this manifest and upload it to this Laravel Vapors' instance s3 bucket. You can upload it anywhere but it makes the most sense to upload at the root level for now. Also make sure that you click the permissions tab and grant public read access.

Picture of AWS s3 console, granting public read access

Now that we have a simple manifest uploaded, we will need to serve it. Go ahead and add a similar route like we did with the sitemap. We want a GET request at /manifest.json which should return a redirect to our manifest.json that we just uploaded to s3.

1Route::get('/manifest.json', function () {
2 return response()->redirectTo(Storage::url('manifest.json'), 302, [
3 'Content-Type' => 'application/json',
4 'Cache-Control' => 'public, max-age=3600',
5 ]);

If you uploaded everything correctly, you should be able to visit your Vapor URL at /manifest.json and see your uploaded file contents. Now let's test some things out. In Chrome, you can see any complaints/warnings by going to dev tools => Application and there should be a Manifest tab on the left there. 

Pro tip: It's important to note the redirect here. While we could simply return the contents of our manifest.json file with the Storage facade and appropriate headers, this has other implications that you would need to address. To keep things simple for this example, just make sure you do this redirect and also upload your icons publicly in s3.

Picture of Chrome dev tools Application Manifest

This is where we can start our iterative approach and try to knock out some of these complaints/warnings. As I mentioned before, and you can see in the screenshot above, Chrome does not recognize that my app works offline. That's because I technically don't have a service worker yet, which if I understand everything properly, would be the last step to having a totally legitimate PWA. Now let's discuss a little more in depth about our manifest and some of the options.

Creating A Proper Manifest For Your PWA 

So we have a super simple manifest that defines some of the required properties for your browser to know what to do about installing to your home screen/desktop. As it stands, you should actually be able to do so at this point, albeit Chrome likely has a lot of complaints. Let's hold off on installing it until we have a few more properties defined in our manifest. We want a clean looking "App" on our phone with an icon and everything after all. 

First, I would like to discuss the scope and start_url properties. It's important to note here, that the way we are serving our manifest file, technically means that it is at another domain. Since we are redirecting to the URL of the asset, it is serving up the URL of the s3 bucket, and not the vanity URL where Vapor is at. Because of this, we will need to explicitly write out the entire URL for both scope and start_url and not just "/". However, we could make adjustments to serve the manifest at the current domain (serve the contents of the file in the response instead of a redirect), but it would also have implications on your icon assets that we'll discuss further on.

Let's take a look at our icon assets for the PWA manifest. These are pretty strict, as you can see in some of the complaints Chrome might have for you already. They have to be exactly the width and height as stated. So at a minimum, 192x192 and 512x512. Now let's assume you've gone ahead of uploaded both of those icons sizes to your s3 bucket. Let's go ahead and take a look at what our manifest.json should look like now.

2 "name": "AppNameHere",
3 "short_name": "AppNameHere",
4 "display": "standalone",
5 "scope": "https://<vapor-vanity-url-here>/",
6 "icons": [
7 {
8 "src": "/my192x192.png",
9 "type": "image/png",
10 "sizes": "192x192"
11 },
12 {
13 "src": "/my512x512.png",
14 "type": "image/png",
15 "sizes": "512x512"
16 }
17 ],
18 "background_color": "#333333",
19 "start_url": "https://<vapor-vanity-url-here>/"

Now the last piece we haven't discussed is the background_color. Nothing too special here, we're just telling the browser what color the background should be of the "loading" screen. When you install the PWA and a user opens it up, it will briefly display a loading screen to make it look more like an app. This should contain your logo and the background color will be what you just specified.

If you have uploaded all your icon assets to s3, and are serving your manifest through a redirect as shown above, and also given the explicit full URL of your Vapor instance, then you should be right where I'm at. Everything but a service worker. If you go to the Chrome browser in android, click the three dots dropdown in the top right and you should see a button to "Add to home screen". Assuming all goes well, you should see the logo for your PWA pop up with the app name, and a confirmation to add. You just created and installed your first PWA :).

Important Tips For Your Own Laravel Vapor PWA Adventures

While writing this article I tinkered a lot with my manifest just to try things out and make sure I could explain how to do things. I wanted to summarize the tricks to successfully create your PWA in Laravel Vapor. First and foremost, it requires an understanding of how network requests are made in general. Which we will not thoroughly go into, but if you have made it this far into the article, I assume you probably know a thing or two. There are many ways to skin a cat, and I hope to soon have a less manual and less complicated way of serving our manifest and creating a PWA in the future. That said, we should note exactly what it is we are doing. The route we defined for our manifest is serving a redirect to the manifest asset URL. What that means is its being served from your s3 bucket. So the actual URL where your manifest is at would be something like https://{s3-bucket-name} It's important to understand this to actually get your manifest working properly. If you uploaded your icon assets to your s3 bucket as well, then you should be good to go using a src URL such as below.

2 "icons": [
3 {
4 "src": "/my192iconnamex192.png",
5 "type": "image/png",
6 "sizes": "192x192"
7 }
8 ]

The reason is because of where your manifest is located at. When the browser is reading the manifest file, it then looks for your icons at https://{s3-bucket-name} Assuming you did upload it to s3 publicly, it should work like a charm. Otherwise, supplying a full URL to the icon src would probably work too "https://{s3-bucket-name}" . It's very important to understand that should you run into any challenges yourself. This is the reason we gave an explicit full URL https://{vapor-vanity-url-here}/ to both scope and start_url. It's all due to the redirect over to the s3 bucket. For the PWA to be registered and working properly, it needs to know exactly where the app is at and everything needs to match (your browser is at https://{vapor-vanity-url-here}/). and since the manifest is at the s3 bucket it leads to confusion. Hence the full URL.


I hope you were able to follow these steps and install your PWA as I did. I am excited to discuss my next adventure which will be to create an "Android app" with a PWA. I could be wrong about this, I am assuming that a service worker may be required before one can use Bubblewrap to create an android app. Supposedly all you do is run a couple commands on the command line (maybe even a GUI option too) and give it some information about your PWA and it bundles up an APK for you. There's a couple more steps in between, like uploading your digital assets to verify ownership and such. But I will be taking on this challenge here in the near future and will share my adventures (or misadventures) with you guys. Until next time!

Please sign in or create an account to join the conversation.