Closed14

Next.js 勉強ログ

すてぃんすてぃん

Next.jsのメイン機能について

Next.jsのページ生成はおもにふたつ

  • Static Generation
    ページにアクセスされる前に(ビルド時に)HTMLを生成しておく。配信するのはペラのHTMLなのでブラウザキャッシュが効いたりGoogle botに負担をかけない(SEO効果)。

  • Server-side Rendering
    リクエスト時に都度HTMLを生成する。ユーザーのログイン情報をもとに画面を生成したい場合や、リクエストパラメーターによって画面を切り替える場合に有効。ただし毎リクエストごとにレンダリングするので負荷が高め。

Next.jsではこのふたつをハイブリッドに使用できる。
ページごとに Static Generation なのか Server-side Rendering なのか決定できる。

Routing について

Next.jsのルーティングは pages をルートパスとして、pages 以下のディレクトリ構成がそのままルーティングとして構築される。pages って名前は変更不可?configファイルで変更できるのかな?要調査。

すてぃんすてぃん

Static Generation

ビルド時に外部データが存在しなければ、普通にHTMLを生成するだけ。ReactがHTMLを生成する作業をサーバーサイドでやってファイルとして保存しておくだけ。イメージしやすい。

ビルド時に外部データが必要な場合は、データを必要とするコンポーネントと同じモジュールファイル内で getStaticProps という async 関数を宣言する。
これはビルド時に実行されて、戻り値がコンポーネントに渡される。
内部で必要なデータをデータベースやWebAPIから取得して戻り値にしてやれば、ビルド時にその外部データがコンポーネントに埋め込まれて、外部データを含んだ静的HTMLとして生成される。

すてぃんすてぃん

getStaticProps

GetStaticProps という型が next から提供されているので、それを利用するとよい。戻り値の型がわかる。

export const getStaticProps:GetStaticProps = async() => { ... };

型定義を見たところ、ジェネリクスで戻り値の props の型を指定できるようなので、コンポーネントの Props 定義をそのまま渡しておくのがいいかも。同じモジュール内で宣言してるから型定義の import export も必要ない。

type Props = { hoge: string };
export default function HogeComponent(props: Props) { ... };

export const getStaticProps: GetStaticProps<Props> = async() => { ... };

getStaticPropsexport して機能させることができるのは、pages ディレクトリに含まれるコンポーネントファイルだけ。

すてぃんすてぃん

Next.jsアプリのソースコード上では fetch をクライアント・サーバーどちらで実行されるかを意識することなく使用していいみたい。 import fetch from "hoge-fetch" みたいな記述も不要。

便利だなと思った反面、標準の fetch は型定義が甘いのでTypeScriptではつらい感ある。

すてぃんすてぃん

Server-side Rendering

リクエスト毎にサーバーサイドでHTMLをレンダリングする。リクエスト情報を使用することができる(リクエストパラメーターやHTTPヘッダーなど)。
リクエストされるたびにHTMLを生成する処理が走るため、当然 Static Generation よりもレスポンスは遅くなる。CDNキャッシュも効かなくなる(公式ガイドには「追加の設定なしでは」と書いてあるので、なんとか頑張れはCDNキャッシュを効かせることはできるようだが)。

コンポーネントファイルに getStaticProps ではなく getServerSideProps 関数が設置してあれば、Server-side Rendering の対象になる。

すてぃんすてぃん

getServerSideProps

こちらも GetServerSideProps という型定義が next から提供されているので、これで型付けするといい。

type Props = { hoge: string };
export default function HogeComponent(props: Props) { ... };

export const getServerSideProps: GetServerSideProps<Props> = context => { ... };

第一引数で context を受け取れて(getStaticProps も受け取れる)、その中身にリクエスト情報が詰め込まれているので使用することができる。

getStaticProps 同様に getServerSideProps が設置できるのは pages ディレクトリ内のコンポーネントのみ。多分。

すてぃんすてぃん

Client-side Rendering

クライアントサイドレンダリングももちろん可能。
Static Genration` で対応可能な部分を事前にレンダリングしておいて、ブラウザに読み込まれたらデータフェッチが必要な部分をブラウザJavaScriptで取得してレンダリングしてもらうイメージ。

多分だけど、Next.jsが生成する静的HTMLもあくまでReactアプリで、useEffect でデータ取得とかやってsetState とかできるんじゃないかな。

ここまでの所感として、Server-side Rendering するよりも全部 Static Generation でやって、ユーザー別のデータが必要なところはAPI用意しておいて、SPA同様に各クライアントにフェッチしてもらうのが良さそう。知らんけど。

すてぃんすてぃん

ページパスが外部データに依存している場合

例えばブログの記事ページ。 /blog/{blogId} となることが多いが、 /blog/blog-slug にアクセスされた場合に blog-slug.md に書かれたブログ内容を表示させるにはどのようにすればいいか。

まずpages 配下のコンポーネントのファイル名を [blogId].tsx のように [] で囲む。

そのコンポーネントファイル内に getStaticPaths という async 関数を用意する。getStaticPaths の戻り値でパスの動的な部分(blogId)を指定する(GetStaticPaths型は next から import)。

export const getStaticPaths: GetStaticPaths = async() => { ... };

続いて同じファイル内に getStaticProps を宣言する。getStaticProps の第一引数は params というプロパティを持っていて、その中に blogId という値を持っている。(ファイル名が [blogId].tsx だから blogId が受け取れる)

export const getStaticProps: GetStaticProps = async({ params }) => { /* params.blogId が使用可能 */ };
すてぃんすてぃん

GetStaticPaths もジェネリクスになっていて、型引数にパスの変数を明示的に指定できる。GetStaticProps の二番目の型引数も同様で、指定することで params.blogId を推論することができる。
ディレクトリベースのルーティングなので型を自分で指定しないといけないが、その点に関しては react-router も同様に残念な感じなので仕方ない。

すてぃんすてぃん

PropsParams の型定義を全部書くとこんな感じのテンプレートになるか。

import { GetStaticPaths, GetStaticProps } from "next";

type Props = { hoge: string };

export default function HogeComponent(props: Props) { ... };

type Params = { id: string };

export const getStaticPaths: GetStaticPaths<Params> = async () => {  };

export const getStaticProps: GetStaticProps<Props, Params> = async ({ params }) => {  };

すてぃんすてぃん

ファイル名を [...id].tsx のように3つのドットを付与することで、全てのパスと一致させることができるらしい。

blog/[...id].tsx の場合、 /blog/a, /blog/a/b, /blog/a/b/c など、/blog/以下全てのパスと一致する。

そのときの getStaticPaths の戻り値は

return [
  {
    params: {
      id: ["a", "b", "c"],
    }
]

のようにパラメーターを配列で返却する必要がある。

このとき getStaticProps の第一引数から取れるのも配列らしい。

export const getStaticProps: GetStaticProps = ({ params }) => {
  // params.id === ["a", "b", "c"] 
}

ユースケースがパッと思いつかない上に、TypeScriptと相性が悪そうでなるべく使いたくない気がしてる。

すてぃんすてぃん

ルーティング関連その他

ルーティング情報をコンポーネント内部から参照したい場合

import { useRouter } from "next/router";

独自の 404 ページ

pages/404.tsx ファイルを作成

すてぃんすてぃん

Next.js上にAPIを作成する(API Routes)

pages/api というフォルダを作成して、その中に関数を置いておくだけ。

pages/api/hello.ts
import { NextApiRequest, NextApiResponse } from "next";

export default (req: NextApiRequest, res: NextApiResponse) => {
  res.status(200).json({ text: "HELLO!" });
};

/api/hello` でアクセスできる。

注意点

getStaticPropsgetStaticPaths からAPIにアクセスすることはできない。
それはそう。なぜならそれらの関数はビルド時に実行され、その時点ではNext.jsのサーバーは起動していないはず。データ取得のロジックを括りだして、getStaticProps とAPI関数それぞれから呼び出すのがいいと思う。

preview mode

Static Genaration はビルド時にのみHTMLを生成するため、書きかけの記事をプレビューすることができない。その代わりNext.jsではAPI Routesを利用した preview mode という機能を提供してるらしい。これについては後日調べる。

このスクラップは2021/04/03にクローズされました