もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

Next.js 入門

はじめに

こんばんは。

今日の積本はこちら。

その中から Next.jsの勉強をしました。

こレに習って、自分が勉強した部分を備忘録で残しておきます。

本題

1. Next.jsとは

Reactベースのwebフレームワーク。

主要な技術はこちら

  • React
  • TypeScript
  • webpack

SPAだけではなくマルチページにも対応している。

対応出来るニーズが広いため、その分設計ノウハウが必要になる。

HTMLの作成方法

Next.jsは大きく分けて以下のHTML作成手段を提供している。

それぞれ長所短所あるが、短所はキャッシュ等である程度カバーできる (逆にキャッシュで長所を消してしまう可能性もある)

1. Server Side Rendering(SSR)

リクエストのたびにサーバー側でHTMLを構築する

長所

常に最新の情報を返せる

短所

毎回レンダリングするのでサーバに負荷を与える

2. Static Site Generation(SSG)

事前にHTMLをビルドしておき、ビルド済みのHTMLを返す

長所

すでにHTMLが作られているのでサーバー負荷が低い

短所

HTMLを更新したい場合は再ビルドの必要があるので、更新頻度が高いサービスは不向き

3. Client Side Rendering(CSR)

必要最小限のHTMLのみかえして、その後APIでコンテンツを取得してHTML要素をレンダリングして構築する

長所

SSR同様最新の情報を取得できる

短所

APIで情報を取得後HTMLが構築されるまで何も表示されない

JSファイルが大きくなる傾向がある

2. セットアップ

A. Vercelで実行環境を構築する

サインアップ後 スターターキットで Next.js を選ぶ。

f:id:kojirooooocks:20211102024950p:plain

レポジトリの名前を決めて create

f:id:kojirooooocks:20211102025002p:plain

Create Teamは自分ひとりなので skip

f:id:kojirooooocks:20211102025011p:plain

Buildを待つと出来上がるのでその後ダッシューボードへ

f:id:kojirooooocks:20211102025023p:plain

その後 Visit を押下するとデプロイされたアプリケーションを表示できる。

f:id:kojirooooocks:20211102025034p:plain

B. ローカル環境構築

作成された git repositoryを cloneしてnodeのバージョンを合わせる。

本書ではnodeのバージョンが 14.17.0 だった。

自分のローカルのバージョンは以下だったので、nodeenvでバージョンを合わせるようにした。

$ node -v
v16.11.0


$ nodenv install 14.17.0
Downloading node-v14.17.0-darwin-x64.tar.gz...
-> https://nodejs.org/dist/v14.17.0/node-v14.17.0-darwin-x64.tar.gz
Installing node-v14.17.0-darwin-x64...
Installed node-v14.17.0-darwin-x64 to /path/to/.anyenv/envs/nodenv/versions/14.17.0


$ nodenv versions  
  14.17.0
* 16.11.0 (set by /path/to/.anyenv/envs/nodenv/version)


$ nodenv local 14.17.0


$ node -v
v14.17.0

その後 npm i で各種ライブラリをインストール後 npm run dev で開発サーバを立ち上げて、ブラウザアクセスが可能になる

f:id:kojirooooocks:20211102025049p:plain

C. Next.jsのファイル構成

$ tree -L 3 .
.
├── README.md
├── next.config.js
├── node_modules
│   ├── @babel
│   ├── ...
│   ├── ...
│   ├── ...
│   └── yocto-queue
├── package-lock.json
├── package.json
├── pages
│   ├── _app.js
│   ├── api
│   │   └── hello.js
│   └── index.js
├── public
│   ├── favicon.ico
│   └── vercel.svg
├── styles
│   ├── Home.module.css
│   └── globals.css
└── yarn.lock
ルーティング

pages/ 以下にファイルを配置していくことでルーティングが決定する。

例)

  • pages/users/index.js => /users
  • pages/users/profile.js => /users/profile
  • pages/users/[id].js => /users/{id}
API

pages/api/ 配下にリクエストハンドラーをセットすることでAPIが提供できる。

const handler = (req, res) => {
  const body = { message: "プロフィールだよ"}
  res.statusCode = 200;
  res.json(body)
};

export default handler

f:id:kojirooooocks:20211102025103p:plain

D. srcディレクトリは勝手に識別してくれる

特に設定を書かなくても src は勝手に認識してくれる。

また、 pages ディレクトリは src/ 以下に配置しても認識してくれる。

E. TypeScript化が容易である

TypeScriptも簡単に導入可能。

以下のコマンドを実行し、TSをインストールする。

$ npm i -D typescript @types/react

pages/index.js pages/_app.js の拡張子を .tsx に変更する。

その後ビルドし直すと next-env.d.ts tsconfig.json が作成される。

これで導入が完了。

3. 静的サイトの生成

Next.jsはページの特性に合わせてSSG or SSR or CSR などを切り替えることが出来る。

本書のP53ページに記載されているフローチャートが、とても理解しやすく、最適解の助けになるものだった。

ぜひ一読してもらいたい。

A. 今までの課題

今までの SSG の課題は2つ

  • ページ量にSSGのビルド時間が比例して大きくなる
  • データ更新の都度、ビルドするという燃費の悪さ

Next.jsはこの弱点を Incremental Static Regeneration(ISR)というしくみで克服している。

B. 静的生成の際に使用されるAPI

Next.jsのページ生成の静的生成において、非常に重要なAPIが以下。

  • getStaticProps

ページの静的生成に外部からデータを取得して、page componentにpropsとして渡すことが出来る。

ページ生成に外部からデータを取る必要がない場合は定義の必要なし。

/**
 * 静的生成時にデータの取得・出力をする
 * @param context 
 * @returns 
 */
export async function getStaticProps(context) {
  const res = await fetch('https://.../users')
  const users = await res.json();
  return { props: { users }}
}
  • getStaticPaths

ページの静的生成時に動的ルートを取得・出力できる

users/{id} のように動的なURLでも 静的生成可能。

つまり事前に全ルートのファイルを事前作成する。

/**
 * params.idに1 or 2を渡す
 * これで、getStaticPropsがそれを扱えるようになる
 * @returns 
 */
export async function getStaticPaths() {
  return {
    paths: [
      { 
        params: { id: 1 }
      },
      { 
        params: { id: 2 }
      }
    ],
    fallback: false
  }
}
export async function getStaticProps({ parmas }) {
  // params.idに1 or 2がはいる
  const res = await fetch(`https://.../users/${params.id}`)
  const user = await res.json();
  return { props: { user }}
}

決め打ちの数値だけではなく getStaticPaths() でもfetchを使って、対象のID群を抜き出して渡すことも可能。

export async function getStaticPaths() {
  const res = await fetch(`https://.../items`)
  const items = await res.json()
  return {
    paths: items.map((item) => ({ params: { id: item.id }})),
    fallback: false
  }
}

ただ、上記のような取得方法だと id が何万件もあるようなものでは、更新されるたびに作るという作業が非効率で時間もかかりすぎる。

Next.jsはこの解決法に「段階的に静的生成する」というアプローチを取る。

それが、 fallback: true

C. fallback: true

fallback: true を指定すると、事前生成されたページが見つからない場合、そのページを生成する。

事前に生成するのではなく、リクエストが来た段階でファイルの存在を判定し、なければ生成するという形。

当然あれば、それを返す。これで大量のページが存在するサイトでも、すべてをビルド時に生成する必要がなくなる。

D. fallback: 'blocking'

fallback: true のデメリットとして、ファイルが存在せずページを生成するフローの場合、バックグラウンドで該当ページの生成が終わるまでは、空のHTMLが返ることになる。

ユーザーの表示的にはよく見るローディングページなどになると思う。

生成が完了したらJSで表示の更新が行われる。これを起こさせないのが、 fallback: 'blocking'

fallback: 'blocking' を指定すると、ファイルが存在しないページへのリクエストの場合、最初にからのHTMLを返すことを許さない。

バックグラウンドのページ生成が終わるまでHTML表示を待たせることになる。

SEO対策が必要なページに適している。

この fallback に関しても本では図解でわかりやすく解説されていたので、ぜひ読んでほしい。

E. Incremental Static Regeneration

今までの中で、まだ解決されてない問題が以下

一度生成されたページをどうやって更新するか

これを解決するのは、 getStaticProps()revalidate オプション

export async function getStaticProps({ parmas }) {
  // params.idに1 or 2がはいる
  const res = await fetch(`https://.../users/${params.id}`)
  const user = await res.json();
  
  // revalidate = 再生成をする間隔(秒数 = 下記だと100秒)
  return { props: { user }, revalidate: 100 }
}

これによりページ更新のためにアプリケーション全体を再ビルドしなくて良くなった。

4. 静的生成に取り組む下準備

第5章以降でGithubAPIを題材に取り組む前の下準備が紹介されている。

  1. Githubのtoken取得
  2. GithubAPIを扱うための Octokit をインストール
  3. fetch用のロジックを作成
  4. 実際にAPIを実行して取得してみる
  5. Github OAuth Appのセッティング

実際にAPIを実行して取得してみる

import { octokit } from "@/utils/fetcher"
import { InferGetStaticPropsType } from "next"

export type PageProps = InferGetStaticPropsType<typeof getStaticProps>

export const getStaticProps = async () => {
  const repos = await octokit.request(
    "GET /users/{username}/repos", 
    { username: 'kojirock5260' }
  )
  return { props: { repos }}
}

export default function Page(props: PageProps) {
  console.log(props.repos.data.map(v => {
    const { name, full_name } = v
    return { name, full_name }
  }));
  return <div>Hellow Next.js</div>
}

f:id:kojirooooocks:20211103031227p:plain

Github OAuth Appのセッティング

f:id:kojirooooocks:20211103031242p:plain

5. 実際に作ってみる

本に記載されている githubのコードを写経して作成してみた。

トップページ

f:id:kojirooooocks:20211106020621p:plain

ユーザー検索結果

f:id:kojirooooocks:20211106020617p:plain

自分のページ(認証前)

f:id:kojirooooocks:20211106020607p:plain

自分のページ(認証後)

f:id:kojirooooocks:20211106020603p:plain

commit一覧

f:id:kojirooooocks:20211106020600p:plain

一部TSの型エラーが出ていたので、制約を緩めれば今でもこの本のコード通りに動くと思われる。

終わりに

初期設定はとても簡単なものでした。

また、 fallbackrevalidate なども実際本記載のサンプルアプリケーションを使って動きを確認できて、理解を深めることが出来ました。

react自体もあまりやったことないので、今度はreactの本も勉強してみようと思いました。

現場からは以上です。