🙂

Next.js(App Router)とReactでよく使う内容をわかりやすくまとめてみた

2023/10/31に公開

Next.jsとは

Vercelによって開発されているReactベースのフレームワークです。
https://nextjs.org/

クライアントサイドレンダリング(CSR)、SSR(サーバーサイドレンダリング)、SSG(静的サイト生成)、ISR(インクリメンタルスタティックリジェネレーション)が含まれ、SPA(シングルページアプリケーション)の開発もサポートしています。Next.jsを使用することで、これらの異なる手法をプロジェクトのニーズに応じて選択し、組み合わせることができます。
https://zenn.dev/akino/articles/78479998efef55
https://qiita.com/shinkai_/items/79e539b614ac52e48ca4

Hot Module Replacement

Next.jsはデフォルトでHMR(Hot Module Replacement)をサポートしているため、コードの変更が即座にブラウザに反映されます。
https://qiita.com/haradakunihiko/items/40486ec2b6b9aea119bb
https://nextjs.org/docs/architecture/fast-refresh

プロジェクトの作成

デフォルトでTypeScriptが同梱されるようになったので --tsは不要です。
今回はnpmを使用して最新のバージョンでプロジェクトを作成します。

npx create-next-app@latest <プロジェクト名>

npx create-next-app@13 <プロジェクト名> # バージョンを指定する方法、今回は13

プロジェクト名に大文字は使用できません。使用した場合、以下のエラー文が表示されます。

name can no longer contain capital letters

途中質問されますので答えます。

Ok to proceed? (y) # yと入力
✔ What is your project named? … test # 先ほどプロジェクト名を入力していない場合はここで入力
✔ Would you like to use TypeScript? … No / Yes # Yesを選択
✔ Would you like to use ESLint? … No / Yes # Yesを選択
✔ Would you like to use Tailwind CSS? … No / Yes # Yesを選択
✔ Would you like to use `src/` directory? … No / Yes # Yesを選択
✔ Would you like to use App Router? (recommended)? … No / Yes # Yesを選択
? Would you like to customize the default import alias (@/*)? › No / Yes # Yesを選択
? What import alias would you like configured? › @/* # enter

https://zenn.dev/ikkik/articles/51d97ff70bd0da

package.jsonには以下が追記されています。

package.json
{
{
  "name": "clasp",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "react": "^18",
    "react-dom": "^18",
    "next": "14.0.1"
  },
  "devDependencies": {
    "typescript": "^5",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "eslint": "^8",
    "eslint-config-next": "14.0.1"
  }
}

開発環境を起動

npm run dev

http://localhost:3000/ にアクセスして以下の表示を確認してください。

HTTPSで立ち上げる方法

https://nextjs.org/blog/next-13-5

package.jsonのdevスクリプトに--experimental-httpsを追記してください。

package.json
  "scripts": {
    "dev": "next dev --experimental-https",

あとはいつも通り開発環境を起動してください。

npm run dev

Vercelにデプロイする

GitHubのリポジトリを作成は済ませておいてください。

下記の手順でGitHubのリポジトリをVercelに連携させます。

Vercelにログインしてください。
https://vercel.com/login

Add New...を選択

Projectを選択してください。

デプロイしたいリポジトリのImportを選択

Deployを選択してください。

Continue to Dashboardを選択

Visitを選択すると、デプロイされたページにアクセスできます。

next/link

主に静的なリンク、つまり事前に定義された、変更されないURLにユーザーを送るために使われ、HTMLの <a>タグのように使います。

また、リンクがビューポート(ユーザーの画面に表示されている範囲)に入ると、リンクの先のページに必要なJavaScriptやデータのプリフェッチング(バックグラウンドでデータを読み込み始める)を自動的に行うので、ページ間のナビゲーションを非常に高速にします。

import Link from 'next/link';

export default function Navbar() {
  return (
    <nav>
      <Link href="/about"><a>About</a></Link>
      <Link href="/services"><a>Services</a></Link>
      <Link href="/contact"><a>Contact</a></Link>
    </nav>
  );
}

next/router

ユーザーのアクションやアプリケーションの状態に基づいて動的にページ遷移を行いたい場合に使います。例えば、フォームの送信後にユーザーを特定のページにリダイレクトしたり、特定の条件下で自動的にユーザーを異なるページに遷移させる場合などです。

next/routerは、next/linkとは異なり、自動的にプリフェッチングを行わず、ページ遷移をトリガーすると、その時点で必要なリソースの読み込みが始まります。

useRouterフックを通じてアクセスでき、pushreplaceなどのメソッドを提供し、コード内から直接URLを操作できます。

import { useRouter } from 'next/router';

export default function Login() {
  const router = useRouter();

  const login = () => {
    // ログイン処理後に/homeにリダイレクトする
    router.push('home');
  }

  return (
    <button onClick={login}>Log In</button>
  );
}

.env

https://nextjs-ja-translation-docs.vercel.app/docs/basic-features/environment-variables

.gitignoreファイルに.envを追記してください。

.gitignore
.env

Next.js は、サーバーサイドでのみ使用される環境変数とクライアントサイドで利用する環境変数を区別しています。

サーバーサイド

.env
ENDPOINT='https://example.com/'
page.tsx
const endPoint = process.env.ENDPOINT || '';

クライアントサイド

環境変数を使用する('use client';など)には、環境変数名をNEXT_PUBLIC_で始める必要があります。

.env
NEXT_PUBLIC_ENDPOINT='https://example.com/'
page.tsx
const endPoint = process.env.NEXT_PUBLIC_ENDPOINT || '';

静的ファイルの生成を行う

https://nextjs.org/docs/pages/building-your-application/deploying/static-exports

next.config.mjsに下記を追記して静的エクスポートを有効にしてください。

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // 追記
};

export default nextConfig;

その後下記のコマンドで静的なHTMLファイルとして出力します。

npm run build

プロジェクト直下にoutというフォルダができたことを確認してください。
前回作成したファイルがある場合でもoutフォルダ内の内容は新しく生成された静的ファイルで上書きされるので問題ありません。

'use client';という指示子を使用している場合

'use client';という指示子は、Next.js 12.2以降で導入された新しい機能、Server ComponentsとClient Componentsを区別するためのものです。'use client';をファイルの最初に記述することで、そのコンポーネントがクライアントサイドでのみ実行されることをNext.jsに指示します。

またNext.jsにおいてクライアントサイドで安全に使用できる環境変数は、変数名がNEXT_PUBLIC_で始まるものに限られます。

上記の理由から次のように記述して使用してください。

.env
NEXT_PUBLIC_ENDPOINT='https://example.com/'

.gitignoreファイルにも.envを追記

.gitignore
.env
page.tsx
const endPoint = process.env.NEXT_PUBLIC_LAMBDA_ENDPOINT || '';

APIハンドラの変更点

APIハンドラにおいてfetchAPIに似た新しいResponseオブジェクトが導入されています。従来の res.status().json()などのメソッドチェーンを使う代わりに、new Response() コンストラクタやユーティリティ関数を使ってレスポンスを生成する方法に変更されています。
https://nextjs.org/docs/app/api-reference/functions/next-response

useRouter

https://nextjs.org/docs/pages/api-reference/functions/use-router

Unhandled Runtime Error Error: NextRouter was not mounted. と表示される場合

下記が表示される場合の解決方法です。
Unhandled Runtime Error Error: NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted

下記のように記述していないか確認してください。

import { useRouter } from 'next/router';

appディレクトリではnext/routerではなくnext/navigationからuseRouterをimportする必要があるためエラーが表示されます。import先を修正してください。
https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#step-4-migrating-routing-hooks

import { useRouter } from 'next/navigation';

favicon

app以下にicon.icoというファイル名で作成するとローカルでも反映されます。
https://nextjs.org/docs/app/api-reference/file-conventions/metadata/app-icons
https://qiita.com/nisaji/items/5aaa4b743dc5bf668e8a

imgタグのESLint警告

Next.jsで、imgタグを使うと以下のような警告が出ることがあります。

Using <img> could result in slower LCP and higher bandwidth. Consider using <Image /> from next/image to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element",

このエラーメッセージは、Next.jsプロジェクトで<img>要素を使用して画像を表示している場合に表示されます。Next.jsでは、画像の最適化とパフォーマンス向上のために、<Image>コンポーネントを使用することが推奨されています。
https://nextjs.org/docs/messages/no-img-element

@next/next/no-img-elementのエラー

例えばS3にデプロイする場合などにnext/imageを使用しているとうまく表示されないので、<img> タグを使用するようにしました。
<img> タグを使用すると@next/next/no-img-elementのエラーがエディター上に表示されるのでESLintの設定ファイルに追記します。

.eslintrc
{
  "extends": "next/core-web-vitals",
  "rules": {                            //追記
    "@next/next/no-img-element": "off"  //追記
  }
}

https://qiita.com/toshikisugiyama/items/9d9ada2de0cedb03a21e

page.module.css

CSS Modulesではグローバルセレクター(body, id セレクタなど)の使用がスタイルの衝突を避けるために制限され、スタイルは主にクラスセレクタを通じて適用されます。
https://github.com/vercel/next.js/discussions/31705

指定した場合は下記のようなエラーが出ます。
Syntax error: Selector "body" is not pure (pure selectors must contain at least one local class or id)

解決方法としては要素に特定のclassNameを使用するか、このルールをグローバルCSSファイルに記述する方法があります。

また、下記のようにクラスセレクタ(この場合は .name)と組み合わせることで記述することができます。

.name input[type='text'] {
  width: 100%;
}

下記の場合はどちらもクラスを記述してください。

.btn button[type='submit']:active::after,
.btn button[type='submit']:hover::after {
  width: 100%;
}

globals.css

プロジェクト全体にわたって直接適用されるスタイルを定義します。
.nameのようなクラスは記述してもエラーにはなりませんが、効きません。page.module.cssに記述してください。

末尾のスラッシュを有効にする

trailingSlashの設定を追加してください。

設定を有効にすることで設定前は、/about//aboutにリダイレクトされていますが、/aboutのようなURLは/about/へリダイレクトされるようになります。

next.config.mjs
module.exports = {
  trailingSlash: true, //追記
}

https://nextjs-ja-translation-docs.vercel.app/docs/api-reference/next.config.js/trailing-slash

Next.jsアプリケーションでサードパーティのスクリプトやiframeを効率的に扱うためのユーティリティやコンポーネントを提供するライブラリ

style属性に記述する必要があるので、page.module.cssなどのスタイルは適用されませんでした。
またiframeタグで使用する下記についても記述できず、自動で再生することができませんでした。
ループ再生やミュートでの再生はparam属性に記述すれば問題ありませんでした。

frameBorder='0'
allow='accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'
allowFullScreen

https://zenn.dev/chot/articles/introduction-of-next-third-parties
https://nextjs.org/docs/app/building-your-application/optimizing/third-party-libraries

iframeタグで[Violation] Forced reflow while executing JavaScript took 35msと表示される場合

loading='lazy'を追記してください。
遅延読み込みを利用することで、ページロード時に実行されるJavaScriptの量を減らし、それによって発生する可能性のある強制的なレイアウト再計算(Forced Reflow)を避けることができます。

 <iframe
width=''
height=''
src=''
frameBorder='0'
allow='accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'
allowFullScreen
loading='lazy'
></iframe>

環境によってパスを変更したい場合

主にサブディレクトリにNext.jsのプロジェクトを置く場合のpublic以下のimageの呼び出しなどで使用すると思います。

https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration
https://qiita.com/hiropy0123/items/02ab91f69dbfa4e2797f
https://qiita.com/nanasi-1/items/78b3b30c22b55c6c53c4#実際のページで使う

.env.developmentと.env.productionの.envファイルを作成します。
それぞれの環境変数は下記の場合に使用されます。これを使用して環境によってパスを変更します。
.env.developmentnpm run dev実施時(ローカル環境)
.env.productionnpm run build実施時(本番=静的な生成を行った場合)

.それぞれの.envファイルの記述

.env.development
NEXT_PUBLIC_BASE_PATH =/
BASE_PATH =/
.env.production
NEXT_PUBLIC_BASE_PATH =/subdirectory-name
BASE_PATH =/subdirectory-name

使用例

const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';

<img
src={`${basePath}/images/icon.png`}
/>

Reactとは

UI(ユーザーインターフェース)を構築するためのオープンソースのJavaScriptライブラリです。Facebookによって開発されています。
https://ja.react.dev/

Reactの主な特徴は下記の5点かなと思います。

コンポーネントベースのアーキテクチャ:

UIを独立した再利用可能なコンポーネントに分割して構築します。これにより、コードの保守性が向上し、開発プロセスが効率化されます。

宣言的なビュー

宣言的にビューを記述することで、アプリケーションの各状態に対応するビューを設計できます。これにより、コードの可読性が向上します。
https://qiita.com/espritfort_tech/items/c10c0d54feff69dffb05

仮想DOM

仮想DOMを使用することで、実際のDOMに変更が必要な場合にのみ、最小限の操作で更新を行います。これにより、アプリケーションのパフォーマンスが向上します。
https://zenn.dev/ak/articles/00616eb99523c2

JSX

JSXと呼ばれるXML風の構文(例: const element = <h1>Hello, world!</h1>; )を使用して記述するので、見た目に関するコードが書くやすく、見やすくなっています。

データフローの単一方向性

データが一方向に流れる設計を採用しています。例えば、コンポーネントの状態(state)が変更されると、その変更が親コンポーネントから子コンポーネントへと反映されます。
これにより、アプリケーションのデータフローが予測しやすく、管理しやすいです。

React.memo

https://ja.react.dev/reference/react/memo
プロップスが変更されない限り、コンポーネントの再レンダリングが抑制されます。
また下記のように2通りの記述方法があります。

デフォルトエクスポート(Default Export)

プロップスを受け取らないfooterのような完全に静的なコンテンツを表示する場合に適しています。

import React from 'react';

const Modal = () => {
  return (
    <div className={styles['modal']}>
<p>これはモーダルです</p>
   </div>
  );
};

export default React.memo(Modal);

名前付きエクスポート(Named Export)

propsがある場合は型を定義して、TypeScriptを使用している場合に型安全が向上させた方が良いです。

import React, { memo } from 'react';

export interface ModalProps {
  id: string;
}

export const Modal = memo(({ id }: ModalProps) => { return (
    <div className={styles['modal']}>
<p>これはモーダルです</p>
   </div>
  );
};

Modal .displayName = 'Modal';

Suspense

SuspenseはReact 16.6以降で利用可能です。
https://ja.react.dev/reference/react/Suspense

ReactのSuspenseコンポーネントは、Reactが提供する機能で、非同期操作(データフェッチ、遅延ローディングのコンポーネントなど)をより簡単に扱うための仕組みです。Suspenseを使用することで、非同期に読み込まれるコンポーネントやデータが利用可能になるまでの間、フォールバックとして任意のReact要素(例えばローディングインジケータ)を表示することができます。

React.lazyと組み合わせて使うことで、コンポーネントを非同期にロードし、初期ページロード時のJavaScriptファイルのサイズを小さく保つことができます。これにより、パフォーマンスを向上させることが可能です。

fallback={<div>Loading...</div>}は記述しなくてもエラーになりません。

import React, { Suspense } from 'react';

// 非同期で読み込むコンポーネント(React.lazyを使用)
const ModalComponent = React.lazy(() => import('./ModalComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        {/* ModalComponentが読み込まれるまで、"Loading..." が表示される */}
        <ModalComponent />
      </Suspense>
    </div>
  );
}

useContext コンポーネント間のデータ共有

詳しく下記の記事で書いています。
https://zenn.dev/nenenemo/articles/1ed50829c27a0f

Attempted import error: 'useForm' is not exported from 'react-hook-form' (imported as 'useForm').

react-hook-formはフォーム管理ライブラリであり、主にクライアントサイドでのユーザーインタラクションを扱います。

よって、'use client';ディレクティブを Next.jsのファイルの先頭に記述することにより、そのファイルがクライアントサイド専用であると Next.js に明示的に指示すると解決すると思います。
https://stackoverflow.com/questions/78196654/attempted-import-error-useform-is-not-exported-from-react-hook-form-import

終わりに

何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉

Discussion