Turning a website into a pwa app!

July 29th, 2021

For this article I am going to be turning my website, that you are on. Into a downloadable PWA.

This will help show how you can turn already existing websites into an app for your customers / users / peers to have easy access to your content and allow you to have all of your technical factors of your business in one place.

Turning a next.js website into a pwa app!

To start we are going to need to include a dependency. next-pwa

npm i next-pwa

Assets for the app

We are also going to need some images. You can include more but the base pwa requires two images at 512x512 and 192x192. However, I am going to be using the pwa-asset-generator which I learned from this blog article by Milind Soorya. I use maskable from the article above to generate a 1000x1000px size image. (Rename the file to icon.png or just change the icon.png in the command for your filename) Then add this to the root directory of your project. So when the command has to be ran you can locate the file easily without typing in some long path. So head over there if you would like more detail about using this but here is the command you should run. npx pwa-asset-generator icon.png icons

  • npx allows us to use a npm package without saving it to package.json.
  • pwa-asset-generator is the package we want to use.
  • icon.png the image we are using to generate the assets. (hence the large size)
  • icons the folder where we want the generated images to be placed

Drag the folder to your public folder and we will reference them later in one of the files we have to add. Also, you want to look for and copy this output from the asset generator:

[
  {
    "src": "icons/manifest-icon-192.png",
    "sizes": "192x192",
    "type": "image/png",
    "purpose": "maskable any"
  },
  {
    "src": "icons/manifest-icon-512.png",
    "sizes": "512x512",
    "type": "image/png",
    "purpose": "maskable any"
  }
]

Wait before starting your dev server. As the next file we add will require you to restart your app.

Officially Getting Started

Now we have all of the dependencies we need.

Let's add our next.config.js file this will include the pwa dependency at build time.

As I am figuring this out as I write this I start with this snip-it from vercel's next.js pwa example.

const withPWA = require('next-pwa')
const runtimeCaching = require('next-pwa/cache')

module.exports = withPWA({
  pwa: {
    dest: 'public',
    runtimeCaching,
  },
})

Adding the manifest.json

I am now adding the manifest.json There are tools out there to generate this but I have just changed the snip-it from this pwa blog article. Then added the file to the public folder in my directory.

{
    "short_name": "ERBriggs",
    "name": "ERBriggs Developer Portfolio",
    "icons": [
        {
            "src": "icons/manifest-icon-192.png",
            "sizes": "192x192",
            "type": "image/png",
            "purpose": "maskable any"
        },
        {
            "src": "icons/manifest-icon-512.png",
            "sizes": "512x512",
            "type": "image/png",
            "purpose": "maskable any"
        }
    ],
    "start_url": "/?source=pwa",
    "background_color": "#6baa75ff",
    "display": "standalone",
    "scope": "/",
    "theme_color": "#cbff4dff",
    "shortcuts": [
    ],
    "description": "The portfolio for Ellis Briggs. A Web developer."
  }

Once that has been added we can move onto our next step.

Adding the _document.js file.

This file is used to add the meta tags to your files so people will be able to download your app from any page.

Here is what I have added to the file. It includes some of the output from the asset generator.

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head>
        <meta
            name="viewport"
            content="width=device-width, initial-scale=1.0"
        />
        <link rel="manifest" href="./manifest.json" />
        <link
            href="/favicon-16x16.png"
            rel="icon"
            type="image/png"
            sizes="16x16"
        />
        <link
            href="/favicon-32x32.png"
            rel="icon"
            type="image/png"
            sizes="32x32"
        />
        <link rel="apple-touch-icon" href="/apple-icon.png"></link>
        
        <link rel="apple-touch-icon" href="icons/apple-icon-180.png"/>

        <meta name="apple-mobile-web-app-capable" content="yes"/>

        <link rel="apple-touch-startup-image" href="icons/apple-splash-2048-2732.jpg" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2732-2048.jpg" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1668-2388.jpg" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2388-1668.jpg" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1536-2048.jpg" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2048-1536.jpg" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1668-2224.jpg" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2224-1668.jpg" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1620-2160.jpg" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2160-1620.jpg" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1284-2778.jpg" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2778-1284.jpg" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1170-2532.jpg" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2532-1170.jpg" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1125-2436.jpg" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2436-1125.jpg" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1242-2688.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2688-1242.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-828-1792.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1792-828.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1242-2208.jpg" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-750-1334.jpg" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1334-750.jpg" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-640-1136.jpg" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"/>
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1136-640.jpg" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"/>
        <meta name="theme-color" content="#cbff4dff" />
    </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

Once you have configured this file to your apps specific needs. Then you should be able to build your app and install the basic version of a pwa.

If you have not built your app before run: npm run build then npm start

Conclusion

Overall, this was quite easy but I did run into a few errors while doing this, like placing the manifest.json in the public folder instead of the root as of Next.js 9.1.

Next I will be showing how you can make your app available offline or supply an offline page that will show as a back-up for pages without currently loaded content.

Thank you for reading this and I hope this can help someone get started on their journey to making a great pwa.

Back to Home