Next.js 学習メモ

投稿日 : 2021-12-21

更新日 : 2022-05-02

本記事は、Next.jsチュートリアルをもとに、私自身が特に重要そうだなと思った部分をコンパクトにまとめた記事(備忘録)になります。
適宜加筆、修正予定です。

Why Next.js

  • SEO に強い SPA 開発
  • パフォーマンスを効率化する仕組み
  • ルーティングが楽
  • API の作成がかんたん(プロジェクトの中でバックエンドの API を作れる)
  • TypeScript を簡単に導入できる

環境構築

Next.js で環境構築するメリット

  • Babel+Webpack の複雑な環境設定が不要
  • Code Splitting のような最適化設定が不要
  • パフォーマンスや SEO のための Pre-render 設定が不要
  • Rendering のタイミングを選択できる
    • SSR  レンダリングをサーバ側でしたり
    • SSG  静的サイトジェネレーション
  • サーバーサイドの処理を簡単に実装できる

手順

  1. Node.js をインストールする。(ver.10.13 以降)
  2. 下記コマンドを実行
npx create-next-app

※チュートリアルで使用したライブラリのインストール

# markdownのパースに必要
npm install gray-matter

# markdownのレンダリングに必要
npm install remark remark-html

# 日付のフォーマットに必要
npm install date-fns

ルーティング

pages ディレクトリで管理

ex)
pages/index.js → /
pages/posts/first_post.js → posts/first_post

Client-Side Navigation とは?

クライアントサイド・ナビゲーションとは、JavaScript を使用してページの遷移を行うことで、ブラウザが行うデフォルトのナビゲーションよりも高速に行うことができます。
<a>タグなどで遷移 → ページがすべて再読み込みされる
<Link>タグを利用することで、Production モードの際に、Link タグの JS を Prefetch されるので、表示の高速化につなげることができる。

className などの属性を追加する必要がある場合は、Link タグではなく、a タグに追加する。
※Link タグの下に入れ子で a タグを利用する。

Asset, Metadata, CSS

  • 画像などの静的データは、public ディレクトリに配置する。
  • <img>タグではなく、<Image>タグを使うといい感じに Next.js 側で調整してくれる。
  • リサイズ、最適化、および WebP などの最新フォーマットでの画像提供が可能に。ビューポートが小さいデバイスに大きな画像を配信することがなくなります。
  • CMS などの外部データソースでホストされている場合でも、最適化してくれる。
  • Next.js は、ビルド時に画像を最適化するのではなく、ユーザーからのリクエストに応じてオンデマンドで画像を最適化する。

画像

import Image from "next/image";

// next.jsが用意しているImageタグを使う
// Imageタグのsrc属性には絶対パスを記載する。
// Publicに保持している画像を表示したい場合は下記のように指定。
const image = () => {
  return (
    <>
      <Image
        priority
        src="/sauna_boy.png"
        className={utilStyles.borderCircle}
        height={144}
        width={144}
        alt={name}
      />
    </>
  );
};

Metadata

import Head from "next/head";

// next.jsが用意しているHeadタグを使う
const head = () => {
  return (
    <>
      <Head>
        <title>First Post</title>
      </Head>
    </>
  );
};

CSS

/**
 styled-jsxを利用して
 React Component内でCSSを書きたい時
 CSS-in-JSライブラリによって記法はいろいろあるっぽい。
 */

<style jsx>{`
  …
`}</style>

CSS Modules を利用してコンポーネント単位で CSS を 適用する方法

① CSS ファイルを用意する。

.container {
  max-width: 36rem;
  padding: 0 1rem;
  margin: 3rem auto 6rem;
}

② style を適用したいコンポーネント内で css ファイルを Import し、className に記載する

import styles from "./layout.module.css";

export default function Layout({ children }) {
  return <div className={styles.container}>{children}</div>;
}
ブラウザ上でレンダリングされるときは、CSS Module がユニークなクラス名を生成するので、CSS Module を利用している限りは、クラス名が衝突することはない。

Global に CSS を適用したい時

グローバル CSS ファイルを読み込むために、pages/_app.js というファイルを以下の内容で作成。

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}
  • App コンポーネントは Top レベル
  • グローバル CSS ファイルは、どこに置いても、どんな名前を使ってもよい
  • 公式チュートリアルでは Top レベルに style ディレクトリを切り、そこに global.css を定義。その css を pages/_app.js に import することでグローバルに CSS を適用していた。
  • css の tips(公式)

https://nextjs.org/learn/basics/assets-metadata-css/styling-tips

Pre-rendering and Data Fetching

Next.js  は基本的に、全てのページを プリレンダリングする。

Next.js は、クライアントサイドの JavaScript ですべてを処理するのではなく、各ページの HTML を事前に生成します。プリレンダリングを行うことで、パフォーマンスや SEO の向上につながります。

プリレンダリングの形式は 2 つある。具体的には HTML の生成されるタイミングが異なる。また、ページごとにレンダリングの方法を指定できる。

  • SSG(Static Generation) (※Next.js 公式は SSG を推奨している)
ビルド時に HTML を生成するプリレンダリング方式です。プリレンダリングされた HTML は、各リクエストで再利用されます。ページを一度作成して CDN で配信することで、リクエストごとにサーバーがページをレンダリングするよりもはるかに高速になる ◎
  • SSR(Server-side Rendering)
サーバーサイドレンダリングは、リクエストごとに HTML を生成するプリレンダリング方式です。

プリレンダリングの使い分け

ユーザーがリクエストする前に、このページを事前にレンダリングすることができるかどうか。
→ 事前にレンダリングできる場合は、SSG がよい。
頻繁に更新されるデータを表示するページでは、リクエストのたびにページの内容が変わる。
→ Server-side Rendering  を選択する。

SSG

getStaticProps

ビルド時にデータを fetch する必要がある場合
getStaticProps 関数を使用する。
getStaticProps 関数内でデータを fetch して Props としてデータを渡すことができる。(ex, file system, api, DB...)

import { getSortedPostsData } from "../lib/posts";

// getStaticPropsは、サーバー側で実行される関数
// = ブラウザ側に渡すこと無く、DBのクエリなども発行できちゃう。
// getStaticPropsは、pagesディレクトリのページでのみ使用可能
export async function getStaticProps() {
  const allPostsData = getSortedPostsData();

  return {
    props: {
      allPostsData,
    },
  };
}

export default function Home({ allPostsData }) {
  return (
    <div>
      // 描画内容
    </div>
   )
}
注:Next.js は、クライアントとサーバーの両方で fetch()を実装しています。インポートする必要はありません。

Server-side Rendering

getServerSidePropsの実行タイミング:ページをリクエストしたタイミングで、サーバーにAPIリクエストを送信し、getServerSidePropsを実行する。

Server-side Rendering を使用するには、ページから getStaticProps ではなく getServerSideProps をエクスポートする必要があります。
// request時にデータを取得する必要がある場合のみ使用する。

export async function getServerSideProps(context) {
  return {
    props: {
      // props for your component
    },
  };
}

Client Side Rendering

プリレンダリングする必要がそもそもない場合は Client Side Rendering を選択することもできる。外部データを必要としないページのロード → クライアント側からデータを取得する。(ユーザのダッシュボード画面等頻繁に更新されるページ、SEO の対策が必要でない場合など)
Client Side Rendering を使用してデータを取得する場合は、SWR と言われる React hooks を使用するのが推奨されている。

SWR

npm install swr
// 使用例

import useSWR from "swr";

function Profile() {
  // JSON データを使用する通常の RESTful API の場合、
  // まずネイティブの fetch をラップした fetcher 関数を作成する必要があります
  const fetcher = (...args) => fetch(...args).then((res) => res.json());

  // 第2引数にfetcher関数を渡す。
  const { data, error } = useSWR("/api/user", fetch);
  if (error) return <div>failed to load</div>;
  if (!data) return <div>loading...</div>;
  return <div>hello {data.name}!</div>;
}

Dynamic Routes

動的ルーティングで、静的ページを生成する方法。/posts/<id>として、id ごとに 動的に静的ページを生成したい時、getStaticPaths という非同期関数をエクスポートします。この関数では、id に指定できる値のリストを返す。

// id に指定できる値のリストの例
// 文字列のリストではなく、オブジェクトの配列を返却しなければならない。
// 各オブジェクトは、params キーを持ち、id キーを持つオブジェクトを含んでいなければなりません
//(ファイル名に[id]を使用しているため)。


[
  {
    params: {
      id: "ssg-ssr",
    },
  },
  {
    params: {
      id: "pre-rendering",
    },
  },
];

流れとしては、

  1. /posts/[id].js 内に、getStaticPaths定義し、id の配列 を返却する。
  2. /posts/[id].js 内に、getStaticProps定義し、id に紐づくデータを取得する。
  3. 2 で取得したデータを Props として渡す。
export async function getAllPostIds() {
  // Instead of the file system,
  // fetch post data from an external API endpoint
  const res = await fetch("..");
  const posts = await res.json();
  return posts.map((post) => {
    return {
      params: {
        id: post.id,
      },
    };
  });
}

getStaticPaths関数の return に含まれる fallback オプションについて

export async function getStaticPaths() {
  // Return a list of possible value for id
  const paths = getAllPostsIds();
  return {
    paths,
    fallback: false,
  };
}
  • fallback: false の場合、
getStaticPath で生成されたパス以外を指定された場合に 404 のページを返却する。
  • fallback: true の場合、
getStaticPath で生成されたパス以外を指定された場合に 404 のページを返却しない。
  • fallback: blocking の場合、
新しいパスはサーバーサイドで getStaticProps を使ってレンダリングされ、将来のリクエストに備えてキャッシュされるため、パスの生成は 1 回のみ実行される

Dynamic Route の拡張について

[連想配列].js として、getStaticPaths のパスの指定を下記のようにすることで、階層をより深くした動的パスのルーティングが生成ができる。

// pages/posts/[...id].js matches /posts/a,
// but also /posts/a/b, /posts/a/b/c and so on.
return [
  {
    params: {
      // Statically Generates /posts/a/b/c
      id: ["a", "b", "c"],
    },
  },
  //...
];

Error Page のカスタム

https://nextjs.org/docs/advanced-features/custom-error-page

// pages/404.jsを作成し、Custom404()関数内に表示したい内容を返却する。
export default function Custom404() {
  return <h1>404 - Page Not Found</h1>;
}


// ※500エラーの時なども同様

API Routes

API エンドポイントを Node.js のサーバーレス関数として作成できる。

API Routes 作成手順

  1. pages ディレクトリ配下に api ディレクトリ作成
  2. api ディレクトリ配下に hoge.js ファイルを作成
  3. 下記の用な感じで handler を定義してあげる
// pages/hoge.js
export default function handler(req, res) {
  res.status(200).json({ text: "Hello" });
}

http://localhost:3000/api/hoge にアクセスすると上記の API が叩かれる ※ API Routes 配下では、getStaticProps or getStaticPaths は使用するべきではない。
→ サーバサイドで実行されるべきコード、ブラウザ側に渡したくないコード(DB のクエリ投げたり等)は、getStaticProps or getStaticPaths を使用する。

Vercel を利用したデプロイ方法

とりあえずこれ見ましょう。
https://nextjs.org/learn/basics/deploying-nextjs-app/deploy
チュートリアルの成果物
https://nextjs-tutorial-w8f.vercel.app

プロジェクトの TypeScript 化

# 1. tsconfig.jsonファイル作成
touch tsconfig.json

# 2. npm run dev する。→tsconfig.jsonに設定が書き込みされる。

# 3. typescript関連のパッケージをインストール
# → next-env.d.tsファイルが自動で作成される。
npm install --save-dev typescript @types/react @types/node