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() => { ... };
getStaticProps
を export
して機能させることができるのは、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
も同様に残念な感じなので仕方ない。
Props
と Params
の型定義を全部書くとこんな感じのテンプレートになるか。
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
というフォルダを作成して、その中に関数を置いておくだけ。
import { NextApiRequest, NextApiResponse } from "next";
export default (req: NextApiRequest, res: NextApiResponse) => {
res.status(200).json({ text: "HELLO!" });
};
/api/hello` でアクセスできる。
注意点
getStaticProps
や getStaticPaths
からAPIにアクセスすることはできない。
それはそう。なぜならそれらの関数はビルド時に実行され、その時点ではNext.jsのサーバーは起動していないはず。データ取得のロジックを括りだして、getStaticProps
とAPI関数それぞれから呼び出すのがいいと思う。
preview mode
Static Genaration はビルド時にのみHTMLを生成するため、書きかけの記事をプレビューすることができない。その代わりNext.jsではAPI Routesを利用した preview mode という機能を提供してるらしい。これについては後日調べる。
Next.jsのプロジェクトディレクトリ、トップに src
フォルダを置いてその中の pages
フォルダを置くことでも動作するらしい。
ソースコードディレクトリのトップは src
が見慣れているためありがたい。