Next.js を使って Chrome からインストール可能な PWA(desktop PWA) 環境を2秒で作ります(ついでに now で remote でも動かせるようにします)

10 分くらいはかかると思います

motivation

chrome73 から desktop PWA がきているのでそれを手軽に実感するやつ作ろうと思います

https://developers.google.com/web/updates/2019/03/nic73#pwas-everywhere

engines

$ node -v; npm -v
v11.0.0
6.4.1

set up

$ mkdir -p pwa-app/pages; cd pwa-app
$ npm init -y; npm i -SE next react react-dom
$ echo 'export default () => <div>Welcome to next.js!</div>' > pages/index.js
$ echo 'export default () => <div>pwa start!</div>' > pages/pwa.js
$ next

static resources

Next.js は静的リソースの置き場所として /static を用意している https://github.com/zeit/next.js/#static-file-serving-eg-images

$ mkdir static

sw.js

fetch ハンドラの記述が必須なのでそれだけ書いとく

$ echo 'self.addEventListener("fetch", event => {});' > static/sw.js

manifest.json

generator があり、これを使うと icon とかを用意できるので特に問題なければこれ使えば楽

https://app-manifest.firebaseapp.com/

"display":"standalone", "Scope": "/",, "start_url": "/pwa" としておけばあとはまあなんでも ok

↑ で出来た zip を解凍してその中身を /static の下に配置する

$ tree -L 2 static/
static/
├── images
│   └── icons
├── manifest.json
└── sw.js

components

SW の install script と manifest.json の読み込みタグをぶち込む

$ touch sw-register.js
import React, { PureComponent } from "react";

class SWRegister extends PureComponent {
  componentDidMount() {
    if ("serviceWorker" in navigator) {
      navigator.serviceWorker
        .register("/sw.js")
        .then(() => {
          console.log("service worker registration successful");
        })
        .catch(err => {
          console.warn("service worker registration failed", err.message);
        });
    }
  }
  render() {
    return <></>;
  }
}

export default SWRegister;
import Head from "next/head";
import SWRegister from "../sw-register";

function IndexPage() {
  return (
    <>
      <Head>
        <link rel="manifest" href="/static/manifest.json" />
      </Head>
      <p>welcome to next.js!</p>
      <SWRegister />
    </>
  );
}

export default IndexPage;

custom server

/ scope で SW を実行させたいので、 /sw.js の request を /static/sw.js に routing させる

default の next/server では / 配下のアクセスは全て /pages/ へ解決されてしまうため、カスタムサーバを作る

$ touch server.js
const { createServer } = require("http");
const { parse } = require("url");
const next = require("next");

const { PORT, NODE_ENV } = process.env;
const port = parseInt(PORT, 10);
const dev = NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  createServer((req, res) => {
    const parsedUrl = parse(req.url, true);
    const { pathname } = parsedUrl;
    if (pathname === "/sw.js") {
      handle(req, res, { ...parsedUrl, pathname: "/static/sw.js" });
    } else {
      handle(req, res, parsedUrl);
    }
  }).listen(port, err => {
    if (err) throw err;
    console.log(`> Ready on http://localhost:${port}`);
  });
});
$ node server.js

うまくいっていると 右上の 縦三点リーダ「pwa-app」をインストール と出ていると思うのでそこから install 出来ます

install 後、起動すると launch 後に start_url で指定したとこに飛ぶ

now

zeit が提供している Global Serverless Deployments(引用)

https://zeit.co/now

詳しく知りたい人はワイの記事みといてください (v1 の時期の記事なので少し古い)

https://qiita.com/lidqqq/items/11722f5e3225f7806f50

ちなみに 利用には signup が必要(無料で利用可)なのでご注意を

まずは package の install

$ npm i -DE now

now 専用の run-script を用意

"scripts": {
  "now-build": "./node_modules/.bin/next build"
},

必要な file を作ってきます

$ touch .nowignore now.json next.config.js

.nowignore

deploy から除外する file を記述

.next/
.vscode/
node_modules/
README.md

now.json

now 用の設定 file

routes のとこで routing を変更しているとこをがミソ

{
  "name": "pwa-app",
  "version": 2,
  "builds": [{ "src": "package.json", "use": "@now/next" }],
  "routes": [
    {
      "src": "/sw.js",
      "dest": "/static/sw.js"
    }
  ]
}

next.config.js

now で deploy される先の実行環境では target: "serverless" の指定が必須

local と remote で同じ file を使うので、実行 context によって target を変更するように修正

YOUR_LOCAL_MACHINE_HOSTNAME には 適宜 hostname をお願いします

const { hostname } = require("os");

module.exports = {
  target:
    hostname() !== { YOUR_LOCAL_MACHINE_HOSTNAME } ? "serverless" : "server"
};
$ now

deploy された先の URL にアクセスして同じようにインストールできると思います

fin.