Did you know that 31% of all e-commerce sales come from mobile devices?
And this number has not stopped increasing since 2010.
As a web developer with an e-commerce store or working for an e-commerce owner, you should probably try to optimize your clients' mobile shopping experience.
Using a Progressive Web App is precisely what you might need. No strings attached (ok, one string attached… but we’ll get to that) and a powerful mobile experience.
That’s why today we’ll look into Vue PWA development and how it can help you get more traffic, more engagements, and high conversions.
We've already tackled this subject in a past post, this time using Gatsby, but It’s nearly impossible to cover all the benefits of PWAs in just one post. Truth be told, we merely scratched the surface. So this post will go into more detail about why you’d be crazy not to develop a PWA for your site.
More specifically, we’ll look at:
What is a Progressive Web Application?
A Progressive Web Application is a web app built using web-platform features to deliver a user experience on par with a native app on any device with a single code-base. These applications are designed to be capable, reliable, and installable on any device.
PWA is an umbrella term coined by Google engineers to define a set of development principles to build websites.
Applications developed this way keep three principles: reliability, performance, and engagement. These were the criteria set by Alex Russel, developer at Google and arguably the father of PWAs, back in 2015. They make up the fundamental baseline for what can be considered a progressive web application.
Smashing Magazine offers another definition that I think would be worth noting here:
A progressive web application takes advantage of the latest technologies to combine the best of web and mobile apps. Think of it as a website built using web technologies but that acts and feels like an app.
Hence why PWAs are so appealing. They take all the benefits of mobile UX and combine them with the speed and reliability of classic web development. As Nadav Dakner points out, building an app unrelated to your online site means that your users need to go through various steps to obtain the app (search in the App Store, download, and install). PWAs, on the other hand, are your actual site’s pages that get served to your user’s mobile device, and they can be installed to their homepage in just one click.
As we know from the laws of e-commerce, less work for customers always equals more customers.
Once a site has a PWA built and ready to go, Chrome will push it to be installed on a user’s mobile device so long as it meets the following criteria:
It is running under HTTPS - Emphasis on the “S” there. Your site must be secured with an SSL certificate.
It has a Web App Manifest - This is a JSON file that lets you customize various features of your app such as name, colors, design, etc.
It has a Service Worker - This is a JavaScript file that allows your PWA to work offline (to the extent that it is capable, of course) or enables push notifications. It’s essentially the script that is constantly working tirelessly in the background.
Now that we know what a PWA is and what it needs to be endorsed by Chrome, it’s time to see the benefits.
Firefox and Safari support PWAs, but they can only be installed on Android and iOS. iOS support for PWAs is also limited, particularly when it comes to background sync.
The benefits of PWAs
Building and using a progressive web application comes with significant benefits as we've seen earlier. Let’s take a look at five remarkable statistics taken from PWAstats.com, an online community that allows companies to share their direct benefits after switching to PWAs:
“Tinder cut load times from 11.91 seconds to 4.69 seconds with their new PWA. The PWA is 90% smaller than Tinder’s native Android app. User engagement is up across the board on the PWA.”
“Forbes’ PWA test saw 2x increase in average user session length, 6x completion rate, and 20% more impressions. Loads in 0.8s down from 3 to 12s.”
“Trivago saw an increase of 150% for people who add its PWA to the home screen. Increased engagement led to a 97% increase in click outs to hotel offers.”
“Pinterest rebuilt their mobile site as a PWA, and core engagements increased by 60%. They also saw a 44% increase in user-generated ad revenue, and time spent on the site has increased by 40%.
“Twitter Lite saw a 65% increase in pages per session, 75% in Tweets, and 20% decrease in bounce rate. Twitter Lite loads in under 3 seconds for repeat visits even on slow networks.”
Now, these were simply the top five examples that I found to be the most interesting. But there are pages upon pages of other examples just like this, with homegrown businesses seeing tangible benefits from using PWAs.
The bottom line?
PWA’s are getting businesses crazy good results. Offering benefits like:
Increasing traffic: Since PWAs are websites, you can benefit from organic traffic from Google. Your progressive web app can be SEO-driven as opposed to native apps.
Getting higher user engagement: The technologies used to build PWAs bring a lightning-fast experience to any device. Customers are more likely to stay on your website if they experience a better experience.
Decreasing page load times: PWAs are built to be reliable, meaning that they will be and feel fast regardless of the network speed. The service worker installs the PWA in the background by executing JavaScript separately from the main browser making the website quickly for your visitors.
Lowering bounce rates: A better user experience means a lower bounce rate. Since PWAs are capable and reliable no matter the devices and network your users are using, you can expect blazing-fast and optimized performance.
These factors lead to higher conversions and, you guessed it, higher revenue. (a.k.a. free money).
Ok, you’re sold. Of course, you are. After all, I already mentioned that this is one of those rare examples where something isn’t too good to be true and is actually as awesome as it seems. But I did mention there was that one string attached...
It’s a fair amount of work to build a PWA. There’s just no way around it.
But the good news is that we’re here to help. We’re going to build a Vue PWA and show you exactly how we did it to make sure you spend as little time (and effort) as figuring it all out on your own. First, though, let’s take a look at why we’re building a Vue PWA this time around.
Why build a Vue PWA?
Here’s the total, 100% honest truth: there is nothing inherently special about Vue.js for making PWAs—it’s just not their primary focus.
I’d be lying if I said otherwise. So why on earth did we choose to build a Vue PWA? Because while Vue itself isn’t explicitly designed for PWAs, it has a pretty cool tool that is which we wanted to show off: Nuxt.js.
Nuxt.js is like the twin brother of Next (which works for React) but is a powerful resource for building a Vue PWA. Nuxt.js will, essentially, create a PWA that works out-of-the-box. However, you can always change its default options such as name, whether or not it is downloadable to your homepage, giving specific permissions, etc.
Thus, you have a great PWA from the get-go, but you also have a certain level of customization to design your progressive web app specifically to your needs/liking.
As you can imagine, having a tool like Nuxt is a HUGE timesaver and will allow you to reap all the benefits of a Vue PWA without all the painstaking hours it would typically take to build one. And since we’re always looking for ways to optimize developer productivity, Nuxt.js is a great place to start.
Once again, it’s almost free money. So let’s dive into our progressive Vue app example and take a look at how you can actually build one for yourself.
Building a Vue PWA with Nuxt.js
Pre-requisites
Basic understanding of Nuxt.js
A Snipcart account (forever free in test mode)
npm and node.js installed
1. Creating a Nuxt.js project
Getting started with Nuxt is incredibly fast thanks to the npx script create-nuxt-app
. Simply run this command in your terminal:
npx create-nuxt-app YOUR-APP-NAME
When prompted, follow the installation instructions in your terminal.
I selected:
Programming language: JavaScript
Package manager: NPM
UI framework: Tailwind CSS
Nuxt.js: modules: Progressive Web App (PWA)
Rendering mode: Single Page App
Deployment target: Static
Development tools: jsconfig.json
If you forgot to add the PWA module at this stage, don't worry we'll install it later anyways! If you're not familiar with Nuxt.js, you can check out a description of each folder in this section of their official documentation.
Since we're using Tailwind CSS, we'll need to install all the dependencies required by running:
npm install --save-dev @nuxtjs/tailwindcss
Add it to your buildModules
section in the nuxt.config.js
file:
export default {
buildModules: ['@nuxtjs/tailwindcss']
}
Then, generate a config file with the following command:
We'll also retrieve the content from my guides and products from markdown files. Therefore, I'll install the frontmatter-markdown-loader
module that will allow me to retrieve any front-matter inside a JS object.
npm i -D frontmatter-markdown-loader
At this stage, you'll also need to update the nuxt.config.js
file with the following snippets.
const path = require('path')
...
build: {
extend(config, ctx) {
config.module.rules.push({
test: /\.md$/,
loader: 'frontmatter-markdown-loader',
include: path.resolve(__dirname, 'contents'),
})
}
}
Once this is completed, you can serve your project locally using the npm run dev
command and visit localhost:3000 in your browser.
2. Adding content to our web app
As a preliminary step, we'll import content inside our web app. There are multiple ways to go about this. If you are querying an API, you can skip this step altogether. However, since I'm using markdown in this demonstration, I'll store all my files inside a contents/guides
directory. Additionally, I'll create a guides.js
file in the same directory with the following code:
export default [
'coffee',
'accessories'
]
This array will allow me to retrieve all the articles available on the website programmatically. However, you'll need to rename these to the name of your own guide or articles and update it as you add more entries.
3. Creating pages and components
Not familiar with Vue components? Here's a starting guide.
Next, we'll create two pages including a homepage that will list our survival guides as well as a page to read the complete guides. But first, we'll need to modify our layout to include the header and footer.
Open up the default.vue
file inside the .nuxt/layouts
directory and replace the content with the following code:
<template>
<div class="main">
<Header />
<nuxt />
<Footer />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script
id="snipcart"
src="https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.js"
data-api-key="<YOUR_API_KEY>"
></script>
</div>
</template>
<script>
import Header from "~/components/Header.vue";
import Footer from "~/components/Footer.vue";
export default {
components: {
Header,
Footer
}
};
</script>
You can create your own Header.vue
or Footer.vue
component inside the components
directory.
You can also add Snipcart's JS file here as well as its dependencies (don't forget to update the API key). For Snipcart's style sheet, you can include it directly in the nuxt.config.js
file.
...
link: [
{ rel: 'stylesheet', href: 'https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.css' }
]
...
Now to create the homepage, you can edit the index.vue
in the pages
directory with the following code.
<template>
<div class="max-w-screen-2xl mx-auto px-10">
<main>
<div>
<section class="mb-10" v-for="(guide, index) in guides" :key="index">
<div class="post-aside mt-4 mb-4">
<h3 class="mb-5 underline"><nuxt-link :to="guide.attributes.link">{{ guide.attributes.title }}</nuxt-link></h3>
<p>{{ guide.attributes.description }}</p>
</div>
<div class="grid grid-cols-2 sm:grid-cols-3 justify-center gap-8 mb-10">
<article class="" v-for="(product, index) in guide.attributes.products" :key="index">
<img :src="product.image" :alt="product.name">
<p class="font-mono">{{product.name}}</p>
<button
class="buy-button snipcart-add-item mt-6 py-2 px-4 bg-gradient-to-r from-green-400 to-blue-500 hover:from-pink-500 hover:to-yellow-500 text-white font-bold rounded-full shadow-offset hover:shadow-lg transition duration-300"
:data-item-id="product.sku"
:data-item-name="product.name"
:data-item-price="product.price"
:data-item-image="product.image"
:data-item-url="`https://snipcart-nuxtjs-pwa.netlify.com/`">
{{`$${product.price}`}}
</button>
</article>
</div>
</section>
</div>
</main>
</div>
</template>
<script>
import guides from '~/contents/guides/guides.js'
export default {
async asyncData ({ route }) {
const promises = guides.map(guide => import(`~/contents/guides/${guide}.md`))
return { guides: await Promise.all(promises) }
},
head() {
return {
title: "All posts | Nuxt.js PWA Coffee Shop"
}
}
}
</script>
Here, you can import the list of your guides and retrieve the markup and attributes inside the asyncData
function. This function will be called on the server before the page loads or at generation. This way, the content of our guides and products will be available for crawlers.
Note: The asyncData method is only available for pages and will not work inside a component.
You might of also notice that we've created a buy button for each of our products per Snipcart's product definition.
You can now create a page for your guides. Create a guides
directory inside pages
with a file named _slug.vue
.
<template>
<div class="max-w-screen-2xl mx-auto px-10">
<h2 class="text-2xl font-semibold font-mono mb-4">{{ attributes.title }}</h2>
<div v-html="html" class="markdown"></div>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-8">
<article v-for="(product, index) in attributes.products" :key="index">
<img class="mx-auto" :src="`../${product.image}`" :alt="product.name" />
<p class="font-mono">{{product.name}}</p>
<button
class="buy-button snipcart-add-item mt-6 py-2 px-4 bg-gradient-to-r from-green-400 to-blue-500 hover:from-pink-500 hover:to-yellow-500 text-white font-bold rounded-full shadow-offset hover:shadow-lg transition duration-300"
:data-item-id="product.sku"
:data-item-name="product.name"
:data-item-price="product.price"
:data-item-image="product.image"
:data-item-url="`https://snipcart-nuxtjs-pwa.netlify.com${currentUrl}`"
>{{`$${product.price}`}}</button>
</article>
</div>
</div>
</template>
<script>
export default {
layout: "guide",
async asyncData({ params, route }) {
const guideName = params.slug
const markdownContent = await import(`~/contents/guides/${guideName}.md`)
return {
attributes: markdownContent.attributes,
html: markdownContent.html,
currentUrl: route.path
};
},
head() {
return {
title: `${this.attributes.title} | Nuxt.js PWA Coffee Shop`
}
}
};
</script>
Naming the page _slug
will allow you to create dynamic routes. Inside the asyncData
function, you can import the markdown file using the params.slug
variable and create the template of your liking.
Also, if you plan on publishing your website using the npm generate
command, you'll probably want to add the following code inside the configuration file.
import guides from "./contents/guides/guides.js"
...
/*
** Generate dynamic routes
*/
generate: {
fallback: true,
routes: [].concat(guides.map(guide => `guides/${guide}`))
},
...
If this is not specified, Nuxt will only generate the index page as it can't automatically know all the possible dynamic routes.
Turning your SPA into a PWA
Turning your web app into a PWA using Nuxt is easy as 123, and 4..! Simply install the PWA module if you didn't in the beginning:
Add it to your configuration file:
...
modules: [
'@nuxtjs/pwa',
],
...
Optionally, overwrite certain values of the manifest:
...
manifest: {
name: 'Nuxt.js PWA Coffee Shop',
short_name: 'Nuxt.js PWA',
lang: 'en',
display: 'standalone',
},
...
Note: display: "standalone" is what allows your PWA to be opened in its very own window and without any browser navigation. This property is also required on specific platforms to install the PWA locally (i.e., Google Chrome on desktop).
And specify with assets from external domains you'd like to cache. In my case, I'll cache the Snipcart files or dependencies.
workbox: {
runtimeCaching: [
{
urlPattern: 'https://fonts.googleapis.com/.*',
handler: 'cacheFirst',
method: 'GET',
strategyOptions: { cacheableResponse: { statuses: [0, 200] } }
},
{
urlPattern: 'https://fonts.gstatic.com/.*',
handler: 'cacheFirst',
method: 'GET',
strategyOptions: { cacheableResponse: { statuses: [0, 200] } }
},
{
urlPattern: 'https://cdn.snipcart.com/.*',
method: 'GET',
strategyOptions: { cacheableResponse: { statuses: [0, 200] } }
},
{
urlPattern: 'https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js',
handler: 'cacheFirst',
method: 'GET',
strategyOptions: { cacheableResponse: { statuses: [0, 200] } }
}
]
}
At this stage, you should have a fully functional PWA that works on any desktop and mobile platform!
Hosting your PWA on Netlify
Now you'll probably want to publish your web app online. Thankfully, hosting services such as Netlify makes hosting your Nuxt PWA incredibly easy.
First, you'll need to put your project directory on Github, Gitlab, or BitBucket if that's not already the case. Once this is done, you can log in to your Netlify account and link your repository.
When prompted, add in npm run generate
as a build command and dist
as a publish directory.
Once the build is complete, your website will be available at the specified address. Furthermore, any changes you push to your repository's master branch will automatically update your PWA!
Live demo and GitHub repository
Closing thoughts
All in all, working with Nuxt was quite gratifying; I never thought creating a PWA would be as simple as that!
Building this demonstration took me about two days. As a complete newcomer, I felt that building this app came with relatively low frictions. I had some difficulty ensuring that Tailwind CSS was adequately set up, but once I followed the Nuxt/Tailwind documentation instead of Tailwind official documentation all became a breeze.
Let me know in the comments if you tried building a PWA with Vue/Nuxt, and what are your thought about it!