Vue PWA: A Progressive Web Application Example With Nuxt

  • 2019-10-01 02:04 AM
  • 598

Vue PWA: A Progressive Web Application Example With Nuxt

And you’d be right to be skeptical. It’s like my dad always told me, “If something seems too good to be true, it probably is.”

Well I hate to break it to you pops, but you’re finally wrong about something. Because using Progressive Web Apps is exactly like someone offering you free money. No strings attached (ok, one string attached… but we’ll get to that).

That’s why today we’ll be building a Vue PWA and looking at how they can get you more traffic, more engagements, and high conversions.

All it takes is a bit of legwork upfront and from then on, like I said, it’s free money.

Now for some of our more attentive readers, you may be thinking, “Wait, didn’t you guys just run a post on PWA’s with a Gatsby store?”

Yes, yes we did (and thanks for reading that, btw). 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.

Quick review of Progressive Web Applications

Quick review of Progressive Web Applications

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

  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

Statistics showing the benefits of PWAs

Let’s take a look at five remarkable statistics taken from PWAstats.com, an online community which 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).

The bottom line?

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

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

Nuxt is like the twin brother of Next (which works for React), but is a powerful resource for building a Vue PWA. Nuxt 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 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

How to create your own Vue PWA with Nuxt

Pre-requisites

1. Creating a Nuxt 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 snipcart-nuxt-pwa

Note: npx should be available out-of-the-box if you have NPM 5.2.0 or higher!

When prompted, follow the installation instructions in your terminal. If you forgot to add the PWA module at this stage, don’t worry we’ll install later anyways! Once you’ve answered every question, your project’s directory structure should be ready to go. If you’re not familiar with Nuxt, you can check out a description of each folder in this section of their official documentation.

In this demonstration, I’ll also use Sass. Thankfully, doing so with Nuxt is pretty seamless. You can install Sass and sass-loader as a development dependency.

npm i -D node-sass sass-loader

I’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

Very neat!

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

...
css: [
  '@/assets/<your_sass_entrypoint>'
],
...
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 the following url in your browser: localhost:3000.

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 [
  'preparing-for-the-apocalypse',
  'all-about-weapons'
]

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

Next, we’ll create two pages including a homepage which 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 layouts directory and add the following code

<template>
  <div class="main">
    <Header />
    <nuxt />
    <Footer />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
    <script
      id="snipcart"
      src="https://cdn.snipcart.com/scripts/2.0/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 it’s 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/2.0/base/snipcart.min.css' }
]
...

Now to create the homepage, you can edit the index.vue in the pages directory with the following code.

<template>
  <div class="main">
    <main>
      <div>
        <article class="post" v-for="(guide, index) in guides" :key="index">
          <div class="post-aside">
            <small>{{ guide.attributes.date }}</small>
            <h3 class="post-title"><nuxt-link :to="guide.attributes.link">{{ guide.attributes.title }}</nuxt-link></h3>
            <p>{{ guide.attributes.description }}</p>
          </div>
          <div class="products">
            <article v-for="(product, index) in guide.attributes.products" :key="index">
              <img :src="product.image" :alt="product.name">
              <button
                class="buy-button snipcart-add-item"
                :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-nuxt-pwa.netlify.com/`">
                {{`${product.price}`}}
              </button>
              <p class="product-name">{{product.name}}</p>
            </article>
          </div>
        </article>
      </div>
      <Testimonals />
    </main>
  </div>
</template>

<script>
import guides from '~/contents/guides/guides.js'
import Testimonials from '~/components/Testimonials'

export default {
  components: {
    Testimonials,
  },
  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 survival store"
    }
  }
}
</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="single-post">
    <h1>{{ attributes.title }}</h1>
    <p>{{ attributes.date }}</p>
    <span v-html="html" class="markdown"></span>
    <div class="products">
      <article v-for="(product, index) in attributes.products" :key="index">
        <img :src="`../${product.image}`" :alt="product.name" />
        <button
          class="buy-button snipcart-add-item"
          :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-nuxt-pwa.netlify.com${currentUrl}`"
          >{{`${product.price}`}}</button>
        <p class="product-name">{{product.name}}</p>
      </article>
    </div>
    <Bio />
  </div>
</template>

<script>
import Bio from "~/components/Bio";

export default {
  components: {
    Bio,
  },
  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 survival store`
    }
  }
};
</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:

npm i @nuxtjs/pwa

Add it to your configuration file:

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

Optionally, overwrite certain values of the manifest:

...
manifest: {
  name: 'Nuxt.js PWA survival store',
  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 any anything from Google Font’s server and any Snipcart file 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/2.2.2/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 and BitBucket if that’s not already the case. Once this is done, you can log in 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 repo

See live demo

See GitHub repo

Closing thoughts

I’m glad I had this oportunity to try out Nuxt. I’ve already had the chance to explore Next.js in a previous blog post, so I was eager to figure out the differences. 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. Although working with Nuxt was a breeze, I felt like the documentation for their modules were sometimes a bit lacking. I would have liked to see more official examples. I had to try a bunch of different things to make the PWA installable from the browser. Thankfully, I was able to figure out most of my problems by sifting through the Github issues.

Recommended Reading

Tips for building fast and light Vue.js SPA components

Laravel and Vue.js: Why Is This Couple Getting Popular?

How to use Vue.js in a PHP your application

Working With Vuetify Forms and Validation