kakakakakku blog

Weekly Tech Blog: Keep on Learning!

最高すぎる!Gmail のフィルタ設定をデプロイできる CLI「gmailfilters」

無限に届くメールを整理するために Gmail の「フィルタ設定」を使っている人は多いと思う.

  • ラベルを付けたり
  • アーカイブをしたり
  • 削除をしたり

僕自身 Gmail を10年以上(メールを遡ったら2006年頃から)使っているため,歴史的な経緯から「フィルタ設定」が増えすぎてしまって,もはや管理不可能になっていた.もともと「フィルタ設定」には XML 形式で「エクスポート」「インポート」をする機能があるけど,もっとシンプルに設定を記述し,GitHub で管理し,継続的にデプロイする「Gmail Filter as Code」 を実現できたら最高だな!と考えていた.

gmailfilters とは?

gmailfilters を使うと,TOML フォーマットで Gmail フィルタ設定を記述できる.以下の例はnotifications@github.com から届くメールに GitHub ラベルを付ける」という意味になる.そして gmailfilters CLI を使って「エクスポート」をしたり,実際に Gmail に「デプロイ」もできる.今回は Go で実装された gmailfilters を紹介する.導入して1週間だけど,本当に最高すぎる!

[[filter]]
query = "from:notifications@github.com"
label = "GitHub"

github.com

アジェンダ

  1. gmailfilters を使う前に
  2. 認証情報を取得する
  3. gmailfilters をセットアップする
  4. gmailfilters でフィルタ設定をエクスポートする
  5. gmailfilters でフィルタ設定を継続的にデプロイする

1. gmailfilters を使う前に

1-1. フィルタ設定をバックアップする

闇雲に gmailfilters を使うと,今までのフィルタ設定を消してしまう可能性もあるため,バックアップ用途で XML をエクスポートしておく.エクスポートした XML を grep したら「計155件」もフィルタ設定があって驚いた!多すぎる...

$ grep '<id>' mailFilters.xml | wc -l
     155

1-2. フィルタ設定とメール配信設定を精査する

フィルタ設定を精査したところ,不要なフィルタ設定が多くあった.特に「不要なメールを削除するフィルタ設定(通販サイト/旅行サイト/クーポン関連/SaaS 関連など)」が1番多かった.根本的な解決を目指すため,会員登録をしている不要なサービスを全て「退会」し,会員登録をしておくサービスは「メール配信解除 (unsubscribe)」にした.またフィルタ設定は OR 条件なども使えるため,関連するフィルタ設定をリファクタリングした.最終的に「計20件」まで減らせた.スッキリした!

2. 認証情報を取得する

gmailfiltersGoogle API を使うため,事前に「認証情報(OAuth クライアント ID)」を取得しておく必要がある.ザックリと取得までのフローを紹介する.まず Google API Console にアクセスし,新しくプロジェクトを作成する.プロジェクト名は gmailfilters にしておく.

console.developers.google.com

次に「API とサービスを有効化」から「Gmail」を検索し「Gmail API」を有効化する.

f:id:kakku22:20200421130447p:plain

サイドバーから「認証情報」「認証情報を作成」「OAuth クライアント ID」と進む.「OAuth 同意画面(外部)」を作成してから認証情報を作成する.「アプリケーションの種類」「その他」にした.

f:id:kakku22:20200421130501p:plain

最後にプロジェクトページから認証情報を取得する.デフォルトだと client_secret_xxx.json というファイル名になっていた.

f:id:kakku22:20200421130513p:plain

3. gmailfilters をセットアップする

やっと準備が整った!さっそく gmailfilters をセットアップする.インストールは go get で簡単に行える.

$ go get github.com/jessfraz/gmailfilters

$ gmailfilters version
gmailfilters:
 version     :
 git hash    :
 go version  : go1.13.1
 go compiler : gc
 platform    : darwin/amd64

次は取得した認証情報ファイルを環境変数 GMAIL_CREDENTIAL_FILE に設定しておく.今回は direnv を使って export するようにした.

$ export GMAIL_CREDENTIAL_FILE=client_secret.json

環境変数を設定したら gmailfilters コマンドを実行する.表示された URL にアクセスし,権限を付与すると,コードをコピーする画面になる.コードをコピーしたら,入力待機中のままになっている gmailfilters コマンドにそのままペーストする.

$ gmailfilters
Go to the following link in your browser then type the authorization code:
https://accounts.google.com/o/oauth2/auth?xxx

(中略)

INFO[0329] Saving credential file to: /var/folders/xxx/token.json
must pass a path to a gmail filter configuration file

f:id:kakku22:20200421235922p:plain

4. gmailfilters でフィルタ設定をエクスポートする

gmailfilters コマンドと --export オプションを使って,フィルタ設定をエクスポートする.ファイル名は filter.toml にした.

$ gmailfilters --export filter.toml
exporting existing filters...
Exported 20 filters

実際にエクスポートしたフィルタ設定を以下に載せておく.GitHub の README.md では queryarchive など,頭文字は小文字なのに,エクスポートをすると Go の構造体 (Struct) のまま大文字になっている.実装は微妙な感じもするけど,どちらでも使える.

[[Filter]]
Query = "from:notifications@github.com"
Archive = false
Read = false
Delete = false
ToMe = false
ArchiveUnlessToMe = false
Label = "GitHub"
ForwardTo = ""

設定できるフィールドは以下となり,概要と入力値を載せておく.

  • Query : クエリ (条件)
  • QueryOr : OR クエリ (条件)
  • Archive : アーカイブするかどうか (true or false)
  • Read : 既読にするかどうか (true or false)
  • Delete : 削除するかどうか (true or false)
  • ToMe : 自分宛てのメールかどうか (true or false)
  • ArchiveUnlessToMe : 自分宛て以外のメールをアーカイブするかどうか (true or false)
  • Label : ラベルを付ける (ラベル名)
  • ForwardTo : 転送する (メールアドレス)

5. gmailfilters でフィルタ設定を継続的にデプロイする

残るは filter.toml を継続的にデプロイしていく.デプロイする場合は gmailfilters コマンドをそのまま使う.簡単すぎる!

$ gmailfilters filter.toml

Decoding filters from file filter.toml
Updating 20 filters, this might take a bit...
Successfully updated 20 filters

最後に個人的に使っているフィルタ設定を紹介しようと思う.

設定例 : 添付メールに Attachment ラベルを付ける

添付メールを探す場面が多いため,has:attachment クエリで検索をして Attachment ラベルを付けている.

[[filter]]
query = "has:attachment"
label = "Attachment"

設定例 : 1MB を超えるメールに Attachment/Large ラベルを付ける

larger:1M クエリを使うとサイズで検索できる.1MB を超える重すぎるメールは Attachment/Large ラベルを付けている.なお,Gmail のフィルタ設定は階層表現ができるため,gmailfilters では label をスラッシュ区切りにする.

[[filter]]
query = "larger:1M"
label = "Attachment/Large"

さらに gmailfilters はデプロイ時にラベルを新規作成してくれるため,事前にラベルを作っておく必要もなくて便利!以下のログを確認すると Created label と表示されている.

$ gmailfilters filter.toml

Decoding filters from file filter.toml
Updating 20 filters, this might take a bit...
INFO[0002] Created label: Attachment/Large
Successfully updated 20 filters

設定例 : GitHub のリポジトリ名で絞り込む

GitHub のリポジトリ通知は全部読むには多すぎるため,個人的に以下のポリシーで「通知設定 (Notifications)」をしている.

  • 「リリース情報を確認したい」リポジトリ
    • Notifications : Releases only
  • 「プルリクエストまで細かく確認したい」リポジトリ
    • Notifications : Watching

その前提で,GitHub の通知は全て notifications@github.com から届くため,リポジトリ名を subject クエリに設定する.以下は「GitHub の通知に GitHub ラベルを付ける」「GitHub の envoyproxy/envoy の通知に GitHub/Envoy ラベルを付けてアーカイブする」という設定になっている.envoyproxy/envoy の通知には GitHub ラベルも GitHub/Envoy ラベルも付く.

[[filter]]
query = "from:notifications@github.com"
label = "GitHub"

[[filter]]
query = "from:notifications@github.com subject:(envoyproxy/envoy)"
label = "GitHub/Envoy"
archive = true

設定例 : Amazon 購買メールに Amazon ラベルを付ける

Amazon 購買メールは「注文確認」「配達完了」など種類が多く,メールアドレスも異なるため「OR 条件」を使ってフィルタ設定をしている.単純にクエリを書くなら,以下のように OR を使える.

[[filter]]
query = "from:shipment-tracking@amazon.co.jp OR from:auto-confirm@amazon.co.jp OR from:order-update@amazon.co.jp"
label = "Amazon"
archive = true

ただし「OR 条件」が増えると可読性が下がるため,gmailfilters では queryOr フィールドを使って配列を記述できる.最終的に Gmail にデプロイされるフィルタ設定は一緒だけど,個人的に queryOr を使っている.

[[filter]]
queryOr = [
  "from:shipment-tracking@amazon.co.jp",
  "from:auto-confirm@amazon.co.jp",
  "from:order-update@amazon.co.jp"
]
label = "Amazon"
archive = true

まとめ

gmailfilters を使うと,TOML フォーマットで Gmail フィルタ設定を記述できる.現在は filter.toml を GitHub のプライベートリポジトリで管理して,フィルタ設定を継続的にデプロイしている.個人的に今年1番「出会えて良かったツール」と言える.最高すぎる!

さぁ gmailfilters で Gmail のフィルタ設定をデプロイしよう!

f:id:kakku22:20200421130355p:plain

今だからこそ読み直す「エンジニアのための時間管理術」

2006年に出版された「エンジニアのための時間管理術」を2009年(大学院時代)にはじめて読んだ.本書はエンジニアを対象にした書籍であり,その後 SIer でシステムエンジニアとして働きながら何度も読み直した.もともとプロダクティビティを追求することに興味があり,今も続くスタイルの一部は本書から学んだことを参考にしている.それほどに「個人的に影響を受けた1冊」と言える.今までブログに書評記事を書いてなく,最近また読み直す機会があったため,書評記事を書くことにした.

本書のタイトルは「エンジニアのための」と書いてあるけど,原著のタイトルは「Time Management for System Administrators」となり,明確に「システム管理者」を対象に書かれている.本書の冒頭にも「プログラマを対象としていない」と書かれている.とは言え,現在では文化面も技術面も変化し,DevOps や SRE という言葉も浸透した.システム管理者に限らず,エンジニアなら誰でも読める内容になっていると思う.2006年に出版されたこともあり,当然ながら技術的なたとえ話に古さを感じたりするため,そのあたりは読み替えながら読む.

目次

  • 1章「タイムマネジメントの原則」
  • 2章「集中と割り込み」
  • 3章「ルーチン」
  • 4章「サイクルシステム」
  • 5章「サイクルシステム : 作業リストとスケジュール」
  • 6章「サイクルシステム : カレンダーの管理」
  • 7章「サイクルシステム : 人生の目標」
  • 8章「優先順位」
  • 9章「ストレスの管理」
  • 10章「電子メールの管理」
  • 11章「時間の浪費」
  • 12章「文書化」
  • 13章「自動化」

1章「タイムマネジメントの原則」

1章「タイムマネジメントの原則」では,6種類の原則が載っている.

  • タイムマネジメント情報を1つの「データベース」にまとめる
  • 能力は重要な作業のために温存しておく
  • 日課を定め,それらに従う
  • 習慣やモットーを養う
  • 「プロジェクトタイム」の間は集中力を保つ
  • 日常生活の管理にも,仕事で使用するのと同じツールを使用する

例えば,最初に載っている原則の「データベース」の例として PDA (Personal Digital Assistant)PAA (Personal Analog Assistant) が紹介されている.Palm など時代を感じる内容だけど,現在ならスマホを使えば良いし,Trello や Google Calendar など,ツールも多くある.アナログ派ならメモ帳を持ち歩くのも良いと思う.他にも「習慣化の話」「頭の中を整理する必要性」など,今でも重要な原則と言える.

4章「サイクルシステム」

4章「サイクルシステム」を読むと,原著者が考えた「タイムマネジメント戦略」を学べる.大前提として「自分の記憶力を信用するな!」というメッセージがあり,とても共感できる.実際には,先ほど載せた PDA or PAA で以下の4点を管理すると書いてある.

  • 365日分の作業リスト(ほぼ日手帳のような?)
  • 今日のスケジュール
  • 約束のカレンダー
  • メモ(人生目標を書いたりする)

そして,毎日を以下のサイクルで繰り返す.

  • 今日のスケジュールを作成する
  • 今日の作業リストを作成する
  • 優先順位を付け,スケジュールを調整する
  • 予定に取り組む
  • 1日の終わり
  • 会社を出る
  • 繰り返す

「サイクルシステム」は完璧にタイムマネジメントをしながら記録を残すため,いざ試そうとすると比較的重いと思う.昔に試したこともあったけど,現在は守破離のように自分なりに取捨選択している.例えば,朝起きたらすぐに作業リストを作ったり,割り込みが入ったら優先順位を入れ替えたりするのは,今でも実践している.数年前からは朝ではなく,寝る前に翌日の作業リストを作っていたりもする.

7章「サイクルシステム:人生の目標」

「サイクルシステム」の素晴らしい点は,構成要素に「人生目標」が入っていることで,目標を立てないと「単純に作業をうまく進められるようになるだけ」になってしまう.7章「サイクルシステム : 人生の目標」を読むと,以下のように具体的な目標を立てる必要性を学べる.

  • "何を" 実現したいのか
  • "いつ" それを実現したいのか

目標の粒度としては,書籍「How to Get Control of Your Time and Your Life」を引用して,以下のような表が載っている.「仕事の目標」「プライベートの目標」をどちらも立てる点がとても良い.

  • 短期(1ヶ月)
  • 中期(1年)
  • 長期(5年)

個人的には2014年頃から本格的に「人生目標」を運用していて,粒度は以下のように変えている.

  • 短期(3ヶ月)
  • 中期(1年)
  • 長期(2年)

基本的に非公開だけど「中期(1年)目標」の一部はブログにも載せている(意図的に抽象的に書いている).「長期(2年)目標」に関しては,例えば 2017年 から「技術支援を仕事にする or 講師になる」という目標があり,実際に現在の仕事は技術講師だし,その前はプログラミング講師の仕事も経験した.さらに2年以上も継続している「ブログメンタリング活動」「習慣化を支援すること」を強く意識している.よって長期目標をある程度は達成できているように思うし,今後も目標は運用し続けていく.

11章「時間の浪費」

プロダクティビティを追求することに興味があるため,11章「時間の浪費」と類似した書籍も読んでいるけど,時間を捻出するために「時間の浪費を減らす」ことは本当に重要だと思う.何を「時間の浪費」と定義するかは価値観にもよるけど,本書だと「費やした時間に対する利益率が低いこと」と書かれている.具体的に以下の項目に興味があれば(自覚症状があれば)読んでみると良さそう.とは言え,項目だけだとミスリードも起きるため,本書を読んでもらえればと!ただ「時間の浪費」を意識しすぎているからこそ,よく「せっかちすぎる」と揶揄される.

  • 一般的な時間の浪費
    • 作業リストのくだらない項目
    • 大量のメーリングリスト
    • 掲示板や Usenet
    • チャットシステム
    • オフィスへの「立ち寄り」
    • セールスと人材スカウト業者
    • 手動プロセス
  • オフィスでの交流
  • 無駄な会議
  • レンタルビデオで迷う
  • つまらない番組を観ない
  • 洗濯と掃除
  • ハードウェアとソフトウェアのインストール

特徴的な項目としては「レンタルビデオで迷う」という「時間の浪費」が挙げられる.ビデオショップで何を借りようか迷う時間が無駄であるという話で,2006年に出版された書籍だけど,今でも動画配信サービスで同じことが起きる.僕自身は Amazon Prime Video でダラダラ迷わないように「Trello に観る予定の作品名をチケット化」している.まさに本書から学んだ Tips と言える.さらに Trello の Checklist を使ってエピソードの進捗まで管理してる話はポッドキャストでも話した.

omoiyarifm.github.io

13章「自動化」

エンジニアを対象にした本書だからこそ,13章「自動化」もある.「自動化をするべきか見極める方法」「自動化のステップ」など,一般的に参考になる内容から,Shell の alias~/. ssh/configMakefile など,もともと対象だった「システム管理者」のためになる内容も載っている.このあたりは技術面の変化もあるため,参考になるものもあれば,今ならもっと便利なツールがあったりもする.

まとめ

2009年から何度も読み直している「エンジニアのための時間管理術」の書評記事を今さらながら書いた.プロダクティビティ関連の本は多くあるけど,本書はエンジニアを対象にした書籍となる.たとえ話などは古さを感じる面もあるけど,2020年になった今でも読める.リモートワークになりタイムマネジメントに悩んでいるという相談を受けることもあり,参考になる1冊だと思う.全ての章を紹介していないため,興味があったら読んでみてもらえればと!仕事も家庭環境も日々変わるけど,定期的に習慣を見直して,タイムマネジメントを楽しもう!

Next.js 9.3 で使えるようになった getStaticProps と getServerSideProps

2020年3月にリリースされた Next.js 9.3 から,アプリケーションをビルドする戦略として以下3種類の API が使えるようになった.Next.js Blog では「Next-gen Static Site Generation (SSG) Support」と紹介されていた.

  • getStaticProps
    • 「ビルド時」にデータを取得する
  • getStaticPaths
    • 「ビルド時」にデータを取得する(Dynamic Routing を作る)
  • getServerSideProps
    • 「リクエスト時」にデータを取得する

nextjs.org

今までの getInitialProps は?

レンダリング時に API からデータを取得するなど,今まで使ってきた getInitialProps はどうなるの?と気になると思う.ドキュメントに書いてある通り,Next.js 9.3 以降は getInitialProps ではなく getStaticPropsgetServerSideProps を使うことが推奨 (Recommended) になっている.とは言え,今までの getInitialProps も非推奨とまでは書いてなく,引き続き使える.今後実装するなら,getStaticPropsgetServerSideProps を使っていくと良さそう.

nextjs.org

なお,getStaticPropsgetServerSideProps は Node.js コンテキスト(サーバサイド)で実行される.よって,今まではフロントエンドにも対応した fetch API として,例えば isomorphic-unfetch などを使っていたけど,サーバサイドだけなら node-fetch を使える.Next.js 9.3 に対応したサンプルコードも node-fetch を使うように更新されていたりする.

data-fetch テンプレート

実際に getStaticPropsgetServerSideProps を試しながら理解を深めるため,create-next-app で使える data-fetch テンプレートの実装を参考に試した.既に getStaticProps を使った実装に更新されている.さらに今回は TypeScript を使うために with-typescript テンプレートをベースにした.create-next-app に関しては先週の記事にまとめた.

kakakakakku.hatenablog.com

検証環境

今回は Next.js 9.3.5 を使う.

$ npm view next version
9.3.5

また,動作確認をするサンプルコードを pages 直下に実装した.

$ tree pages
pages
├── get-server-side-props.tsx
└── get-static-props.tsx

サンプルコード : get-static-props.tsx

まず,getStaticProps を使ったサンプルコード get-static-props.tsx を載せる.ビルド時に GitHub API から Next.js の Star⭐️ 件数を取得する.また Link コンポーネントを使って遷移できるように /get-server-side-props へのリンクも実装した.

import React from 'react'
import Link from 'next/link'
import fetch from 'node-fetch'

type Props = {
  stars: number
}

function Index({ stars }: Props) {
  return (
    <div>
      <p>Next.js has {stars} ⭐️</p>
      <Link href="/get-server-side-props">
      <a>Go to getServerSideProps</a>
      </Link>
    </div>
  )
}

export async function getStaticProps() {
  const res = await fetch('https://api.github.com/repos/zeit/next.js')
  const json = await res.json()
  return {
    props: {
      stars: json.stargazers_count,
    },
  }
}

export default Index

サンプルコード : get-server-side-props.tsx

次に,getServerSideProps を使ったサンプルコード get-server-side-props.tsx を載せる.リクエスト時に GitHub API から Next.js の Star⭐️ 件数を取得する.また Link コンポーネントを使って遷移できるように /get-static-props へのリンクも実装した.

import React from 'react'
import Link from 'next/link'
import fetch from 'node-fetch'

type Props = {
  stars: number
}

function Index({ stars }: Props) {
  return (
    <div>
      <p>Next.js has {stars} ⭐️</p>
      <Link href="/get-static-props">
        <a>Go to getStaticProps</a>
      </Link>
    </div>
  )
}

export async function getServerSideProps() {
  const res = await fetch('https://api.github.com/repos/zeit/next.js')
  const json = await res.json()
  return {
    props: {
      stars: json.stargazers_count,
    },
  }
}

export default Index

ビルド

yarn build を実行すると,実装された getStaticPropsgetServerSideProps を参考に判定される./get-static-props「● (SSG)」となり /get-server-side-props「λ (Server)」となる.

$ yarn build

(中略)

Page                            Size     First Load JS
┌ ○ /                           1.8 kB         59.9 kB
├ ○ /404                        2.61 kB        60.7 kB
├ λ /get-server-side-props      1.84 kB        59.9 kB
└ ● /get-static-props           1.85 kB        59.9 kB

(中略)

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)(Static)  automatically rendered as static HTML (uses no initial props)(SSG)     automatically generated as static HTML + JSON (uses getStaticProps)

✨  Done in 5.0s.

動作確認

実際に画面にアクセスして確認した.ビルド時の Star⭐️ 件数は 46914 だった(動作確認中にどんどん増えてて驚いた!).ビルド時と変化させるために Star⭐️ を付けたり消したりした.以下にイメージ画像を載せておく.

f:id:kakku22:20200420102656p:plain

1. /get-static-props

getStaticProps の場合はビルド時にデータを取得しているため,素早く表示された.5ms ~ 10ms 程度.直接 http://localhost:3000/get-server-side-props にアクセスすると Content-Typetext/html で返ってきた.Link コンポーネントをクリックしてフロントエンド側で画面遷移をすると Content-Typeapplication/json で以下のように返ってきた.

{
  "pageProps": {
    "stars": 46914
  },
  "__N_SSG": true
}

2. /get-server-side-props

getServerSideProps の場合はリクエスト時にデータを取得しているため,少し時間がかかる.getStaticProps と同じく,直接 http://localhost:3000/get-server-side-props にアクセスすると Content-Typetext/html で返ってきた.Link コンポーネントをクリックしてフロントエンド側で画面遷移をすると Content-Typeapplication/json で以下のように返ってきた.

{
  "pageProps": {
    "stars": 46918
  },
  "__N_SSP": true
}

まとめ

Next.js 9.3 で使えるようになった getStaticPropsgetServerSideProps の理解を整理するためにサンプルコードを実装しながら試した.getStaticProps はビルド時にデータを取得し,getServerSideProps はリクエスト時にデータを取得する.詳しくは Data fetching のドキュメントにも載っている.Static Site Generation (SSG) を使っていくぞー!

nextjs.org

Next.js に入門するのに便利な create-next-app の with-typescript テンプレート

最近 Next.js Learn を使って Next.js に入門した.今度は Next.js でプロトタイプを実装しながら学ぶため,プロジェクトの初期構築として create-next-app を使うことにした.少し調べたところ,create-next-app はもともと Segment 社で実装されて,現在は Next.js を管理する ZEIT 社に譲渡されている.さらに2019年10月に再実装された create-next-app もリリースされている.

nextjs.org

with-typescript テンプレート

create-next-app にテンプレートを指定するオプションがある.試しに Next.js と TypeScript 組み合わせた with-typescript テンプレートを指定したところ,Next.js を学ぶのに便利な構成になっていた.Next.js 9.3 にも対応している.今回は create-next-appwith-typescript テンプレートを使って学べる「計6点」を紹介する.コードを書きながら試行錯誤するなら Next.js Learn よりも使える.

  1. ディレクトリ構造
  2. Link コンポーネント
  3. Layout コンポーネント
  4. getStaticProps 関数
  5. getStaticPaths 関数と Dynamic Routes 機能
  6. API Routes 機能

なお with-typescript 以外のテンプレートは以下にある.

準備

まず npx create-next-app--example オプションを使ってプロジェクトを構築する.構築後に yarn dev を実行すると,すぐに http://localhost:3000 にアクセスできるようになる.

$ npx create-next-app --example with-typescript sandbox-create-next-app-ts
$ cd sandbox-create-next-app-ts
$ yarn dev

f:id:kakku22:20200413214932p:plain

1. ディレクトリ構成

create-next-app を実行した直後は以下のようなディレクトリ構成になっている.node_modules 以外を載せた.議論の余地はあるかもしれないけど,pages/ 以外にコンポーネントを管理する components/ や,型定義を管理する interfaces/ など,最低限このあたりは分割しておこう!という指針になる.

$ tree -I node_modules
.
├── README.md
├── components
│   ├── Layout.tsx
│   ├── List.tsx
│   ├── ListDetail.tsx
│   └── ListItem.tsx
├── interfaces
│   └── index.ts
├── next-env.d.ts
├── package.json
├── pages
│   ├── about.tsx
│   ├── api
│   │   └── users
│   │       └── index.ts
│   ├── index.tsx
│   └── users
│       ├── [id].tsx
│       └── index.tsx
├── tsconfig.json
├── utils
│   └── sample-data.ts
└── yarn.lock

7 directories, 16 files

2. Link コンポーネント

まず,トップページの実装を確認するため pages/index.tsx を読む.すると Link コンポーネントを使って /about へのリンクが実装されている.React Router などを使わずに簡単に実装できる.

import Link from 'next/link'
import Layout from '../components/Layout'

const IndexPage = () => (
  <Layout title="Home | Next.js + TypeScript Example">
    <h1>Hello Next.js 👋</h1>
    <p>
      <Link href="/about">
        <a>About</a>
      </Link>
    </p>
  </Layout>
)

export default IndexPage

3. Layout コンポーネント

画面の1番上には Users ListUsers API など,他にもリンクが実装されている.既に載せた pages/index.tsx を読むと,独自実装の Layout コンポーネントがあり,title 属性が指定されている.コンポーネントの実装を確認するため components/Layout.tsx を読むと,画面共通のレイアウトが実装されている.ここにまた Link コンポーネントがあったり,{children} を使ってコンテンツを受け渡していたり,ベーステンプレートの実装として参考になる.

import * as React from 'react'
import Link from 'next/link'
import Head from 'next/head'

type Props = {
  title?: string
}

const Layout: React.FunctionComponent<Props> = ({
  children,
  title = 'This is the default title',
}) => (
  <div>
    <Head>
      <title>{title}</title>
      <meta charSet="utf-8" />
      <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    </Head>
    <header>
      <nav>
        <Link href="/">
          <a>Home</a>
        </Link>{' '}
        |{' '}
        <Link href="/about">
          <a>About</a>
        </Link>{' '}
        |{' '}
        <Link href="/users">
          <a>Users List</a>
        </Link>{' '}
        | <a href="/api/users">Users API</a>
      </nav>
    </header>
    {children}
    <footer>
      <hr />
      <span>I'm here to stay (Footer)</span>
    </footer>
  </div>
)

export default Layout

4. getStaticProps 関数

次にトップページから Users List をクリックすると,一覧画面に遷移する.

f:id:kakku22:20200413215057p:plain

一覧画面を実装した pages/users/index.tsx を読むと,Next.js 9.3 でリリースされた新機能 getStaticProps 関数を使って実装されている.getStaticProps 関数を使うと,Next.js のビルド時に静的ファイルを生成できるため,不要な SSR (Server-Side Rendering) を減らせる.今回は API ではなく,ファイルデータ utils/sample-data.ts を読み込む実装になっている.当然ながら,全ての一覧画面を静的ファイルにするべきという意味ではなく,あくまでサンプルとして.

なお,AliceBob など,一覧部分は,独自実装の List コンポーネント (components/List.tsx) と ListItem コンポーネント (components/ListItem.tsx) に実装されている.

export const getStaticProps: GetStaticProps = async () => {
  // Example for including static props in a Next.js function component page.
  // Don't forget to include the respective types for any props passed into
  // the component.
  const items: User[] = sampleUserData
  return { props: { items } }
}

5. getStaticPaths 関数と Dynamic Routes 機能

一覧画面で Alice など,特定のユーザーをクリックすると,詳細画面に遷移する.

f:id:kakku22:20200413215135p:plain

詳細画面は pages/users/[id].tsx に実装されている.詳細画面はユーザーごとに異なるため,Next.js の「Dynamic Routes 機能」を使って,任意の id に対応する.そのために Next.js 9.3 でリリースされた新機能 getStaticPaths 関数を使って実装されている.まず getStaticProps 関数で,URL から取得した id を使ってファイルデータを検索し,ビルド時に静的ファイルを生成する.さらに getStaticPaths 関数で,Dynamic Routes 機能に必要なパスのリストを生成する.簡単な実装ではあるけど,参考になる.

import React from 'react'
import { GetStaticProps, GetStaticPaths } from 'next'

import { User } from '../../interfaces'
import { sampleUserData } from '../../utils/sample-data'
import Layout from '../../components/Layout'
import ListDetail from '../../components/ListDetail'

type Props = {
  item?: User
  errors?: string
}

export default class StaticPropsDetail extends React.Component<Props> {
  render() {
    const { item, errors } = this.props

    if (errors) {
      return (
        <Layout title={`Error | Next.js + TypeScript Example`}>
          <p>
            <span style={{ color: 'red' }}>Error:</span> {errors}
          </p>
        </Layout>
      )
    }

    return (
      <Layout
        title={`${
          item ? item.name : 'User Detail'
        } | Next.js + TypeScript Example`}
      >
        {item && <ListDetail item={item} />}
      </Layout>
    )
  }
}

export const getStaticPaths: GetStaticPaths = async () => {
  // Get the paths we want to pre-render based on users
  const paths = sampleUserData.map(user => ({
    params: { id: user.id.toString() },
  }))

  // We'll pre-render only these paths at build time.
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false }
}

// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export const getStaticProps: GetStaticProps = async ({ params }) => {
  try {
    const id = params?.id
    const item = sampleUserData.find(data => data.id === Number(id))
    // By returning { props: item }, the StaticPropsDetail component
    // will receive `item` as a prop at build time
    return { props: { item } }
  } catch (err) {
    return { props: { errors: err.message } }
  }
}

6. API Routes 機能

最後は Next.js で API を実装する「API Routes 機能」で,pages/api/users/index.ts のように pages/api 配下にファイルを置く.すると http://localhost:3000/api/users で API にアクセスできるようになる.

$ curl -s http://localhost:3000/api/users | jq .
[
  {
    "id": 101,
    "name": "Alice"
  },
  {
    "id": 102,
    "name": "Bob"
  },
  {
    "id": 103,
    "name": "Caroline"
  },
  {
    "id": 104,
    "name": "Dave"
  }
]

今回はファイルデータ utils/sample-data.ts をそのまま JSON で返すだけになっているけど,例えば NextApiRequest で HTTP メソッドを判定したり,API Middlewares を使って拡張もできる.最低限の API 実装という観点で参考になる.

import { NextApiRequest, NextApiResponse } from 'next'
import { sampleUserData } from '../../../utils/sample-data'

export default (_: NextApiRequest, res: NextApiResponse) => {
  try {
    if (!Array.isArray(sampleUserData)) {
      throw new Error('Cannot find user data')
    }

    res.status(200).json(sampleUserData)
  } catch (err) {
    res.status(500).json({ statusCode: 500, message: err.message })
  }
}

まとめ

create-next-appwith-typescript テンプレートから Next.js プロジェクトを初期構成すると,Next.js の主要な機能を学べる.一通りの機能を試せる点や Next.js 9.3 をサポートしている点など,Next.js Learn よりも便利だなと感じた.機能ごとに学びたかったら Next.js Learn を使って,コードを書きながら試行錯誤したかったら create-next-app で新規プロジェクトを作ってしまうと良さそう.Next.js Learn の紹介記事も載せておく!

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

Chrome DevTools で「オフライン環境」をエミュレートする

例えば「オフライン対応」のアプリケーションを実装して動作確認をするときなど「オフライン環境」が必要になる場合もある.単純に WiFi をオフにすれば良いこともあるけど,WiFi をオフにせず,部分的にオフライン環境をエミュレートできると便利.具体的には,先週とあるライブコーディングを自宅から配信したときに,WiFi をオフにせず「オフライン環境のテスト」をする必要があった.

Chrome DevTools を使う

developers.google.com

Chrome DevTools を使えば,簡単にオフライン環境をエミュレートできる.Network タブを開き Disable cache の右側にあるプルダウンから「Offline」を選ぶ.すると,設定したタブはオフライン環境になる.

f:id:kakku22:20200410235002p:plain

ネットワーク設定は「計4種類」から選べる.さらに Add... から自由に Network Throttling Profiles を作れる.Download (kb/s)Upload (kb/s)Latency (ms) を自由に設定できるため,低速ネットワーク環境におけるフロントエンドのレンダリング確認などに使える.

  • Online(デフォルト)
  • Fast 3G
  • Slow 3G
  • Offline

f:id:kakku22:20200410235217p:plain

おまけ : Chrome Dino Game も遊べる

オフライン環境をエミュレートすれば Chrome Dino Game も遊べる!とは言え,実はオフライン環境をエミュレートしなくても,直接 chrome://dino/ にアクセスすれば遊べる.気分転換に便利!

f:id:kakku22:20200411000247p:plain

まとめ

今回は Chrome DevTools の小ネタを書いた.Chrome でオフライン環境をエミュレートする場合は Network タブを開き「Offline」を選ぼう!