DEV Community

Ludovic Armand
Ludovic Armand

Posted on • Originally published at snipcart.com

Vue PWA: A Progressive Web Application Development with Nuxt

In a rush? Skip to the Vue PWA development tutorial or the live demo.

Did you know that 31% of all e-commerce sales come from mobile devices?

Mobile Sales PWA

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 make the shopping experience for your (or client) mobile users optimal.

Using a Progressive Web App is exactly 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.

All it takes is a bit of legwork upfront and from then on.

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:

  1. A quick review of Progressive Web Applications
  2. Statistics showing the benefits of PWAs
  3. Why we’re building a Vue PWA this time around
  4. How to create your own Vue PWA

A quick review of Progressive Web Applications

Vue PWA Example

A few weeks ago, we provided a guide to PWA e-commerce with a live Gatsby demo. In it, we provided a progressive web app definition, a few of the major benefits, and some examples of PWA e-commerce sites you can go check out. If you haven’t read that post yet, I definitely recommend doing so as it will provide a great framework for what you’re about to read.

However, there simply isn’t enough time or space to cover everything about PWAs in just one sitting. That’s why we’ll be expanding the original definition from our Gatsby PWA article and go into a bit more depth. Here’s the definition we provided last time:

Progressive Web Application is an umbrella term coined by Google engineers. It is rather a set of development principles than a specific technology or stack.

Applications developed this way keep three principles in mind: reliability, performance, and engagement. These were the criteria set by Alex Russel, developer at Google and arguably the father of PWAs, back in 2015, and they make up the fundamental baseline for what can be considered a progressive web application.

In layman's terms, however, 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. Like Nadav Dakner points out, building an app that is 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-commerceless 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:

  1. It is running under HTTPS - Emphasis on the “S” there. Your site must be secured with an SSL certificate.
  2. 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.
  3. 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). It’s essentially the script that is always 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 some real-life results from famous companies that currently use PWAs.

Statistics showing the benefits of PWAs

Benefits of Vue PWA

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 literally 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. They are increasing traffic, getting higher user engagement, decreasing page load times, and lowering bounce rates. All of these factors lead to higher conversions and, you guessed it, higher revenue. (a.k.a. free money).

Vue PWA Benefits

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 definitely 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?

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 main 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 specifically 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, build 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 certain 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 normally 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 Vue PWA example and take a look at how you can actually build one for yourself.

How to create your own Vue PWA with Nuxt.js

Nuxt PWA

Pre-requisites

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Add it to your buildModules section in the nuxt.config.js file:

export default {
  buildModules: ['@nuxtjs/tailwindcss']
}
Enter fullscreen mode Exit fullscreen mode

Then, generate a config file with the following command:

npx tailwindcss init
Enter fullscreen mode Exit fullscreen mode

Tailwind has a neat fea

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
Enter fullscreen mode Exit fullscreen mode

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'),
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

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'
]
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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' }
]
...

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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}`))
},
...

Enter fullscreen mode Exit fullscreen mode

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:

npm i @nuxtjs/pwa
Enter fullscreen mode Exit fullscreen mode

Add it to your configuration file:

...
modules: [
  '@nuxtjs/pwa',
],
...

Enter fullscreen mode Exit fullscreen mode

Optionally, overwrite certain values of the manifest:

...
manifest: {
  name: 'Nuxt.js PWA Coffee Shop',
  short_name: 'Nuxt.js PWA',
  lang: 'en',
  display: 'standalone',
},
...

Enter fullscreen mode Exit fullscreen mode

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] } }
    }
  ]
}

Enter fullscreen mode Exit fullscreen mode

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.

Netlify Deployment Settings

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 repo

Vue/Nuxt PWA Demo

See live demo

See GitHub repo

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!

Top comments (0)