PWA with Next.js 🔥
Author : JaNakh Pon , August 23, 2021
Tags
What is a PWA(Progressive Web App) ?
PWA - Progressive Web App is a piece of technology, enabling businesses to build web applications that have the look and feel of native mobile apps. These apps are developed using web technologies like JavaScript, HTML, CSS. And further, developers utilize static site generators like Gatsby or VuePress to launch PWAs.
These apps run in the browser but give users the experience of a native app. And they can be:
- Accessed through the mobile home screen
- Give offline app access
- Have a native feature; push notifications
- And much more ...
How to build a PWA with Next.js
We are going to use the same repo from the previous article about next-seo:
Installation & setup
Let's get started by next-pwa dependency to our app:
> cd next-seo-pwa && npm i next-pwa --save
And configure next-pwa
in next.config.js
:
const withPWA = require('next-pwa')
module.exports = withPWA({
pwa: {
dest: 'public',
// disable: process.env.NODE_ENV === 'development',
// register: true,
// scope: '/app',
// sw: 'service-worker.js',
//...
}
})
Implementation
Next, we need to create a file, named "manifest.json" and read more about it here.
Don't forget to add "purpose":"any maskable"
to icon!:
{
"name": "PWA",
"short_name": "PWA",
"display": "standalone",
"orientation": "portrait",
"theme_color": "#FFFFFF",
"background_color": "#FFFFFF",
"start_url": "/",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Now we need to add more meta tags for PWA in _document.tsx
:
import Document, { Html, Head, Main, NextScript } from "next/document";
import React from "react";
const url = `http://localhost:3000/`;
const description = `HaHa | HeHe`
const name = "HaHa"
const social_image = "mstile-150x150.png"
export default class extends Document {
static async getInitialProps(ctx: any) {
return await Document.getInitialProps(ctx);
}
render() {
return (
<Html lang="en" dir="ltr">
<Head>
{/* HERE I AM 👇*/}
<meta name='application-name' content='PWA App' />
<meta name='apple-mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-status-bar-style' content='default' />
<meta name='apple-mobile-web-app-title' content='PWA App' />
<meta name='description' content='Best PWA App in the world' />
<meta name='format-detection' content='telephone=no' />
<meta name='mobile-web-app-capable' content='yes' />
<meta name='msapplication-config' content='/icons/browserconfig.xml' />
<meta name='msapplication-TileColor' content='#2B5797' />
<meta name='msapplication-tap-highlight' content='no' />
<meta name='theme-color' content='#000000' />
<meta
name="keywords"
content="Hello, hola, blah blah"
/>
<meta name="author" content="Ja Nakh Pon" />
<meta name="robots" content="index,follow" />
<meta name="googlebot" content="index,follow" />
<meta name="description" content={description} />
<meta name="theme-color" content="#006ABC" />
{/* openGraph */}
<meta property="og:title" content={name} />
<meta
property="og:description"
content={description}
/>
<meta property="og:type" content={"website"} />
<meta property="og:url" content={url} />
<meta property="og:locale" content="en_IE" />
<meta property="og:site_name" content={name} />
<meta property="og:image" content={social_image} />
{/* Twitter */}
<meta name="twitter:site" content={`@ja_nakh`} />
<meta name="twitter:creator" content={`@ja_nakh`} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={name} />
<meta
name="twitter:description"
content={description}
/>
<meta name="twitter:image" content={social_image} />
{/* Icons */}
<link rel="canonical" href={url} />
<link rel="icon" href="/favicon.ico" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
{/* AND HERE 👇*/}
<link rel="manifest" href="/manifest.json" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#006ABC" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
And now we need to add <title></title>
and viewport
to our _app.ts
:
import '../../styles/globals.css'
import type { AppProps } from 'next/app'
import Head from "next/head";
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Head>
<title>PWA 🔥 </title>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, viewport-fit=cover"
/>
</Head>
<Component {...pageProps} />
</>
)
}
export default MyApp
And, One last thing ☝️, we can also add _offline.tsx
for offline fallback:
import Head from "next/head";
const Offline = () => (
<>
<Head>
<title>PWA</title>
</Head>
<h1>Oops! you are offline!</h1>
</>
);
export default Offline;
Since everything is ready, let's build it and serve it in local. So, we could check the results using Lighthouse
:
> npm run build && npm run start
> \\ Open Dev Tools > Lighthouse > Generate reports
and you should see something like this:

Congrats!!! now you have a PWA app with proper SEO tags 😉.