首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >为原有的 NextJS 构建 PWA

为原有的 NextJS 构建 PWA

作者头像
Innei
发布2021-12-28 11:23:41
发布2021-12-28 11:23:41
1K00
代码可运行
举报
文章被收录于专栏:静之森静之森
运行总次数:0
代码可运行

花了一上午的时间,总算是把 pwa 整上了。先来说说什么是 pwa。

渐进式 Web 应用会在桌面和移动设备上提供可安装的、仿应用的体验,可直接通过 Web 进行构建和交付。它们是快速、可靠的 Web 应用。最重要的是,它们是适用于任何浏览器的 Web 应用。如果你在构建一个 Web 应用,其实已经开始构建渐进式 Web 应用了。

简单来说,支持 pwa 的网站再移动端或者桌面端都可以模拟成设备中的一个 app,存在于主屏幕上。

开始之前

每个 pwa 应用都需要一个 manifest.json, 可能看成是一个配置文件。可以去 https://app-manifest.firebaseapp.com/ 生成。

结构类似是这样的

json

代码语言:javascript
代码运行次数:0
运行
复制
1{
2  "name": "静かな森",
3  "short_name": "静かな森",
4  "theme_color": "#27ae60",
5  "description": "致虚极,守静笃。",
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

但是我这边测试的时候,这个网站的图标生成炸掉了,之后我又找了一个 cli 工具,可以用来生成 icon。可以去 GitHub 搜一下 pwa-asset-generator

准备工作完成后,你可以有如下文件。

代码语言:javascript
代码运行次数:0
运行
复制
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

集成到 NextJs 项目中

首先你需要把以上文件复制到项目根目录的 public 目录中,如果不存在可以新建一个空的目录。

来到 src 目录,新建一个 _document.tsx 改文件用来控制 NexJs 该如何渲染根节点。

这里我们需要添加亿些 meta 标签。

html

代码语言:javascript
代码运行次数:0
运行
复制
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="静かな森" />
5<meta name="apple-mobile-web-app-title" content="静かな森" />
6<meta name="msapplication-tooltip" content="静かな森" />
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

你也可以在这里添加一些其他的 meta 比如苹果设备上显示的图标等等。

那么完成了一步,接下来才是最重要的一步。

首先你需要知道 PWA 应用必须使用 workservice, 换句话说只有使用 workservice 才可以离线访问,这才算得上应用。

部署 WorkService

传统的方案较为繁琐,在这里我们采用 next-offline 来实现。

首先安装 next-offline

sh

代码语言:javascript
代码运行次数:0
运行
复制
1yarn add next-offline

COPY

接着在 next.config.js 中配置如下

js

代码语言:javascript
代码运行次数:0
运行
复制
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

如果你有多个配置则可以采用嵌套写法,如

js

代码语言:javascript
代码运行次数:0
运行
复制
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

安装之后再次启动应用。workservice 已经搞定。就是这么简单。

打开 Chrome devtools,选择 audits 选项,生成报告,你会看到 processive Web app 图标亮了。

生产环境部署

这一步反而是最难的,因为一般我们会使用 nginx 或者其他高性能服务器反代。考虑到缓存和 Headers 不同,大概率会产生不同的问题。

我出现了如下问题:

  • 504 错误,查看 nginx 的缓存时间,建议关闭缓存,因为 workservice 自带缓存。
  • 500 错误,如果使用 pm2 托管 nodejs 应用,查看 pm2 时候超出了内存大小而重启
  • network error,查看 nginx 并发数,由于采用了 workservice 所以单 ip 的并发数比较多,建议设置成一般的两倍
  • js,css 加载 404,查看 nginx 是否开启了防盗链,workservice 请求的时候不带 referrer

附 nginx 反代配置参考

代码语言:javascript
代码运行次数:0
运行
复制
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/
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-05-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开始之前
  • 集成到 NextJs 项目中
  • 部署 WorkService
  • 生产环境部署
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档