Vue PWA: A Progressive Web Application Development with Nuxt
Tags: vue, nuxt, pwa
Published: 2021-09-16 17:36:37

> In a rush? Skip to the [Vue PWA development tutorial](#tutorial) or the [live demo](https://snipcart-nuxtjs-pwa.netlify.app/).

Did you know that [31% of all e-commerce sales](https://snipcart-nuxtjs-pwa.netlify.app/) come from mobile devices?

![Mobile Sales PWA](https://snipcart.com/media/205999/mobile-sales-pwa.gif)

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](https://snipcart.com/blog/pwa-example-ecommerce-gatsby) 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](#review)
2. [Statistics showing the benefits of PWAs](#stats)
3. [Why we’re building a Vue PWA this time around](#VuePWA)
4. [How to create your own Vue PWA](#tutorial)

<h2 id="review">A quick review of Progressive Web Applications</h2>

![Vue PWA Example](https://snipcart.com/media/205995/vue-pwa-example.png)

A few weeks ago, we provided a [guide to PWA e-commerce with a live Gatsby demo](https://snipcart.com/blog/pwa-example-ecommerce-gatsby). 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](https://infrequently.org/2016/09/what-exactly-makes-something-a-progressive-web-app/), 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](https://www.smashingmagazine.com/2016/08/a-beginners-guide-to-progressive-web-apps/) 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](https://inboundjunction.com/what-are-progressive-web-apps) 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-commerce](https://snipcart.com/ecommerce-checkout), *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:

1. **It is running under HTTPS** - Emphasis on the “S” there. Your site *must be* secured with an [SSL certificate.](http://www.howto-expert.com/how-to-get-https-setting-up-ssl-on-your-website/)
2. **It has a [Web App Manifest](https://developers.google.com/web/fundamentals/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](https://developers.google.com/web/fundamentals/primers/service-workers/)** - 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.

<h2 id="stats">Statistics showing the benefits of PWAs</h2>

![Benefits of Vue PWA](https://snipcart.com/media/205994/benefits-of-vue-pwa.png)

Let’s take a look at five remarkable statistics taken from [PWAstats.com](https://www.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](https://snipcart.com/media/205998/vuepwa-top-result.png)

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.

<h2 id="VuePWA">Why build a Vue PWA?</h2>

![Vue PWA](https://snipcart.com/media/205997/vuepwa.png)

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](https://snipcart.com/blog/next-js-ecommerce-tutorial) (which works for [React](https://snipcart.com/blog/react-ecommerce-tutorial)) 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](https://snipcart.com/blog/guide-developer-productivity-tools), 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.

<h2 id="tutorial"> How to create your own Vue PWA with Nuxt.js </h2>

![Nuxt PWA](https://snipcart.com/media/205996/nuxtjs_long.png)

### Pre-requisites

- Basic understanding of [Nuxt.js](https://nuxtjs.org/)
- A [Snipcart account](https://app.snipcart.com/register) (forever free in test mode)snipcart

### 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](https://nuxtjs.org/guide/directory-structure/).

Since we're using Tailwind CSS, we'll need to install all the dependencies required by running:

```bash
npm install --save-dev @nuxtjs/tailwindcss
```

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

```bash
export default {
  buildModules: ['@nuxtjs/tailwindcss']
}
```

Then, generate a config file with the following command:

```bash
npx tailwindcss init
```

> 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.

```bash
npm i -D frontmatter-markdown-loader
```

At this stage, you'll also need to update the `nuxt.config.js` file with the following snippets.

```jsx
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](http://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:

```jsx
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](https://snipcart.com/blog/vue-component-example-tutorial).

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:

```jsx
<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.

```jsx
...
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.

```jsx
<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](https://docs.snipcart.com/configuration/product-definition).

You can now create a page for your guides. Create a `guides` directory inside `pages` with a file named `_slug.vue`.

```jsx
<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.

```jsx
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:

```
npm i @nuxtjs/pwa
```

Add it to your configuration file:

```jsx
...
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.

```jsx
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.

![Netlify Deployment Settings](https://snipcart.com/media/205992/nutlifysettings.png)

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](https://snipcart.com/media/205993/snipcart-nuxtjs-pwa-demo.png)

> See [live demo](https://snipcart-nuxtjs-pwa.netlify.app/)

> See [GitHub repo](https://github.com/snipcart/snipcart-nuxtjs-pwa)

## 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](https://tailwindcss.nuxtjs.org/) 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!