通常Next.jsでService Workerを使う場合は、server.js
などを自前で作成します。
しかしNetlifyは静的なコンテンツ配信を行うサーバーのため上の手法は選択できません。逆に言えば静的なコンテンツとしてディレクトリに存在していれば実行できるようになります。そのための手法をここでは記載します。
追記2018.8.1 |
---|
使用プラグインを変更しました sw-precache-webpack-plugin -> workbox-webpack-plugin |
パッケージのインストール
workbox
webpackを通して動的にバンドルされたファイルをキャッシュしてくれる便利プラグインが存在します。
いくつかあるのですが、以下の記事を参考に今回はworkbox
を採用しました。
Workbox で Service Woker のキャッシュを導入してみた話し、Webpack を添えて | Nagisaのすゝめ
https://blog.nagisa-inc.jp/archives/1132
今回はworkboxをwebpackで使うためworkbox-webpack-plugin
というプラグインをインストールします。
$ yarn add workbox-webpack-plugin -D
next.config.js
next.jsのwebpackをoverrideするため、next.config.js
をルートに作成します。
$ touch next.config.js
next.config.js
は以下のように書きます。
runtimeCaching
に適宜追加する感じです。
const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = {
webpack: config => {
config.plugins.push(
new WorkboxPlugin.GenerateSW({
cacheId: 'workbox',
swDest: 'service-worker.js',
skipWaiting: true,
clientsClaim: false,
runtimeCaching: [
{
urlPattern: '/',
handler: 'networkFirst',
options: {
cacheName: 'page',
expiration: {
maxAgeSeconds: 60 * 60 * 24
}
}
},
{
urlPattern: /\/api\/.+/,
handler: 'networkFirst',
options: {
cacheName: 'api',
expiration: {
maxAgeSeconds: 60 * 60 * 24
}
}
},
{
urlPattern: /\.(png|svg|woff|ttf|eot)/,
handler: 'cacheFirst',
options: {
cacheName: 'assets',
expiration: {
maxAgeSeconds: 60 * 60 * 24 * 14
}
}
}
]
})
);
return config;
}
};
この状態で、next build
を実行すると生成される.next
ディレクトリの直下にservice-worker.js
とprecache-manifest.[hash].js
いうファイルが生成されていることがわかります。
package.json
Netlifyで使用するのはnext export
コマンドを通して生成されたout
ディレクトリが該当します。
よってnext build
によって生成される.next
ディレクトリ内は合致しません。
そこでservice-worker.js
とprecache-manifest.[hash].js
をout
ディレクトリ内にコピーするタスクをpackage.json
に記載します。そしてこのコピーするタスクをnext export
時に実行するようにします。
具体的には以下のようになります。
"scripts": {
"storybook": "start-storybook -s ./ -p 6006",
"dev": "next",
"build": "next build",
"start": "next start",
"export": "npm run build && next export && npm run copySW",
"copySW": "cp .next/service-worker.js out/service-worker.js && cp .next/precache* out/"
},
こうすることでNetlifyで使用するout
ディレクトリにservice-worker.js
たちがコピーされ、使える状態になります。
service-worker.jsの実行
コピーされたservice-worker.js
がコンポーネントで実行されるようにしましょう。
コンポーネントがマウントされたタイミングが望ましいため、componentDidMount
で実行することが適切です。
まずはOfflineSupport.js
というコンポーネントを作成します。
import React, { PureComponent } from 'react';
class OfflineSupport extends PureComponent {
componentDidMount() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./service-worker.js')
.then(() => {
console.log('service worker registration successful');
})
.catch(err => {
console.warn(
'service worker registration failed',
err.message
);
});
}
}
render() {
return null;
}
}
export default OfflineSupport;
これを_app.js
で使いましょう。
import App, { Container } from 'next/app';
import React from 'react';
import OfflineSupport from '../src/components/OfflineSupport';
class RootApp extends App {
render() {
return (
<Container>
<YourComponents />
<OfflineSupport />
</Container>
);
}
}
export default RootApp;
おわり
ここまでの設定をすることでserver.jsなどを自前で作成しなくともNetlifyをService Workerを使うことは可能です。
Lighthouseのスコアもなかなかいい感じになりました。
ぜひ機会があれば試してみてください!