It took me all morning to finish pwa at last. Let's talk about pwa first.
Progressive Web applications will provide installable and application like experience on desktop and mobile devices, which can be built and delivered directly through the Web. They are fast and reliable Web applications. Most importantly, they are Web applications for any browser. If you're building a Web application, you're already building a progressive Web application.
Simply put, the mobile terminal or desktop terminal of a website supporting pwa can be simulated as an app in the device and exist on the home screen.

Before starting
Each pwa application needs a manifest JSON, which may be regarded as a configuration file. Can go https://app-manifest.firebaseapp.com/ Generate.
The structure is similar to this
json
1{ 2 "name": "quietかなSen", 3 "short_name": "quietかなSen", 4 "theme_color": "#27ae60", 5 "description": "To the empty pole, keep quiet.", 6 "background_color": "#2ecc71", 7 "display": "standalone", 8 "scope": "/", 9 "start_url": "/", 10 "lang": "zh-cmn-Hans", 11 "prefer_related_applications": true, 12 "icons": [ 13 { 14 "src": "manifest-icon-192.png", 15 "sizes": "192x192", 16 "type": "image/png", 17 "purpose": "maskable any" 18 }, 19 { 20 "src": "manifest-icon-512.png", 21 "sizes": "512x512", 22 "type": "image/png", 23 "purpose": "maskable any" 24 } 25 ] 26}
COPY
But when I tested here, the icon generation of this website blew up, and then I found a cli tool that can be used to generate icons. You can go to GitHub and search PWA asset generator.
After the preparation, you can have the following documents.
1├── apple-icon-120.png 2├── apple-icon-152.png 3├── apple-icon-167.png 4├── apple-icon-180.png 5├── manifest-icon-192.png 6├── manifest-icon-512.png 7└── manifest.json
COPY
Integration into NextJs project
First, you need to copy the above files to the public directory of the project root directory. If they do not exist, you can create an empty directory.
Go to the src directory and create a new one_ document. The TSX file is used to control how NexJs renders the root node.
Here we need to add some meta tags.
html
1<link rel="manifest" href="/manifest.json" /> 2<meta name="mobile-web-app-capable" content="yes" /> 3<meta name="apple-mobile-web-app-capable" content="yes" /> 4<meta name="application-name" content="quietかなSen" /> 5<meta name="apple-mobile-web-app-title" content="quietかなSen" /> 6<meta name="msapplication-tooltip" content="quietかなSen" /> 7<meta name="theme-color" content="#27ae60" /> 8<meta name="msapplication-navbutton-color" content="#27ae60" /> 9<meta name="msapplication-starturl" content="/" /> 10<meta 11 name="viewport" 12 content="width=device-width, initial-scale=1, shrink-to-fit=no" 13/>
COPY
You can also add some other meta here, such as the icons displayed on Apple devices and so on.
Then you have completed one step, and the next is the most important step.
First of all, you need to know that PWA applications must use workservice. In other words, only workservice can be accessed offline, which can be regarded as an application.
Deploy WorkService
The traditional scheme is cumbersome. Here we use next offline to implement it.
First install next offline
sh
1yarn add next-offline
COPY
Next config. JS is configured as follows
js
1const withOffline = require('next-offline') 2module.exports = withOffline({ 3 workboxOpts: { 4 swDest: process.env.NEXT_EXPORT 5 ? 'service-worker.js' 6 : 'static/service-worker.js', 7 runtimeCaching: [ 8 { 9 urlPattern: /^https?.*/, 10 handler: 'NetworkFirst', 11 options: { cacheName: 'offlineCache', expiration: { maxEntries: 200 } }, 12 }, 13 ], 14 }, 15})
COPY
If you have multiple configurations, you can use nested writing, such as
js
1const withSourceMaps = require('@zeit/next-source-maps')() 2 3const SentryWebpackPlugin = require('@sentry/webpack-plugin') 4const { 5 NEXT_PUBLIC_SENTRY_DSN: SENTRY_DSN, 6 SENTRY_ORG, 7 SENTRY_PROJECT, 8 SENTRY_AUTH_TOKEN, 9 NODE_ENV, 10} = process.env 11 12process.env.SENTRY_DSN = SENTRY_DSN 13 14const isProd = process.env.NODE_ENV === 'production' 15const withBundleAnalyzer = require('@next/bundle-analyzer')({ 16 enabled: process.env.ANALYZE === 'true', 17}) 18const env = require('dotenv').config().parsed || {} 19const withImages = require('next-images') 20const withOffline = require('next-offline') 21const configs = withSourceMaps( 22 withImages( 23 withBundleAnalyzer({ 24 webpack: (config, options) => { 25 if (!options.isServer) { 26 config.resolve.alias['@sentry/node'] = '@sentry/browser' 27 } 28 29 if ( 30 SENTRY_DSN && 31 SENTRY_ORG && 32 SENTRY_PROJECT && 33 SENTRY_AUTH_TOKEN && 34 NODE_ENV === 'production' 35 ) { 36 config.plugins.push( 37 new SentryWebpackPlugin({ 38 include: '.next', 39 ignore: ['node_modules'], 40 urlPrefix: '~/_next', 41 release: options.buildId, 42 }), 43 ) 44 } 45 46 return config 47 }, 48 env: { 49 PORT: 2323, 50 ...env, 51 }, 52 assetPrefix: isProd ? env.ASSETPREFIX || '' : '', 53 async rewrites() { 54 return [ 55 { source: '/sitemap.xml', destination: '/api/sitemap' }, 56 { source: '/feed', destination: '/api/feed' }, 57 { source: '/rss', destination: '/api/feed' }, 58 { source: '/atom.xml', destination: '/api/feed' }, 59 { 60 source: '/service-worker.js', 61 destination: '/_next/static/service-worker.js', 62 }, 63 ] 64 }, 65 experimental: { 66 granularChunks: true, 67 modern: true, 68 }, 69 }), 70 ), 71) 72module.exports = isProd 73 ? withOffline({ 74 workboxOpts: { 75 swDest: process.env.NEXT_EXPORT 76 ? 'service-worker.js' 77 : 'static/service-worker.js', 78 runtimeCaching: [ 79 { 80 urlPattern: /^https?.*/, 81 handler: 'NetworkFirst', 82 options: { 83 cacheName: 'offlineCache', 84 expiration: { 85 maxEntries: 200, 86 }, 87 }, 88 }, 89 ], 90 }, 91 ...configs, 92 }) 93 : configs
COPY
Start the application again after installation. The workservice has been completed. It's that simple.
Open Chrome devtools, select the audits option and generate a report. You will see that the icon of the processed web app is on.

Production environment deployment
This step is the most difficult, because we usually use nginx or other high-performance servers. Considering the difference between cache and Headers, the probability will cause different problems.
I have the following problems:
- 504 error. Check the cache time of nginx. It is recommended to close the cache because the workservice has its own cache.
- 500 error. If pm2 is used to host nodejs application, the memory size is exceeded when viewing pm2 and the application is restarted
- network error, check the number of nginx concurrency. Since workservice is adopted, the number of concurrency of a single ip is relatively large. It is recommended to set it to twice the general number
- js,css load 404, and check whether nginx has enabled the anti-theft chain. When the workservice requests, it does not bring a reference
The reference of nginx reverse configuration is attached
1#PROXY-START/ 2 location ~ (sw.js)$ { 3 proxy_pass http://127.0.0.1:2323; 4 proxy_set_header Host $host; 5 proxy_set_header X-Real-IP $remote_addr; 6 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 7 proxy_set_header REMOTE-HOST $remote_addr; 8 add_header Last-Modified $date_gmt; 9 add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; 10 if_modified_since off; 11 expires off; 12 etag off; 13 } 14 location / 15 { 16 proxy_pass http://127.0.0.1:2323; 17 proxy_set_header Host $host; 18 proxy_set_header X-Real-IP $remote_addr; 19 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 20 proxy_set_header REMOTE-HOST $remote_addr; 21 22 add_header X-Cache $upstream_cache_status; 23 proxy_ignore_headers Set-Cookie Cache-Control expires; 24 add_header Cache-Control no-cache; 25 expires off; 26 } 27 28 29 #PROXY-END/