🎖️

Next.jsでよく使う内容をまとめてみました

2023/10/31に公開

はじめに

基本的にはApp Router構造の場合で記述しています。

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

Reactとは

下記にまとめています。
https://zenn.dev/nenenemo/articles/3f80fac20d2ee3

バージョンごとのルーティング機能

Pages Router(v12以前)

Next.jsのバージョン12まで使用されていたルーティング機能で、/pagesフォルダ直下に配置されたファイルが自動的に各ページに変換されるシステムです。この方式では、ファイルのパスがURLのパスと直接対応しています。

API Routes(v12以前)

Pages Routerの一部で、/pages/apiフォルダに配置されたJavaScriptファイルがAPIエンドポイントとして機能する仕組みです。これにより、Next.jsアプリケーションがサーバーサイドのAPIとしても動作できるようになっています。

v12.2以前

以前はリクエストがNextApiRequest型、レスポンスがNextApiResponse型でしたが、それぞれNextRequestNextResponseに型が変更されています。
https://nextjs.org/docs/app/api-reference/functions/next-request

NextResponse

基本的にはResponse()と同じです。

route.ts
import { NextResponse } from 'next/server';
import { client } from '../../../../libs/client';

export async function GET() {
  try {
    const res = await client.get({
      endpoint: process.env.END_POINT || '',
    });

    return NextResponse.json(res, { status: 200 });
  } catch (error) {
    throw error;
  }
}

https://nextjs.org/docs/app/api-reference/functions/next-response

App Router(v13以降)

Next.jsのバージョン13から導入された新しいルーティング機能で、従来のPages Routerを置き換えます。この新システムでは、/appフォルダに配置されたpage.jsx(またはpage.tsx)ファイルがページとして生成されます。この変更により、より柔軟なルーティングとファイル構成が可能になります。

動的ルーティング

記述の仕方は以下です。

app
├── page.tsx
└── blog
    ├── page.tsx
    └── [slug]
        └── page.tsx

よくある間違い
これだと404エラーになります。

app
├── page.tsx
└── blog
    ├── page.tsx
    └── [id].tsx

Route Handlers (v13.2以降)

App Routerの機能拡張で、APIルーティング機能を提供します。これは、従来のAPI Routesの代わりになる機能で、/app/apiフォルダ内でAPIエンドポイントの設定が可能になります。これにより、アプリケーションのロジックをより統合的に管理できるようになります。

レンダリング方法の確認

Next.jsでは、ビルドする際にプロジェクト内の各ページについてどのレンダリング方法が用いられているかを示すログを出力することができます。
https://qiita.com/whopper1962/items/1d1a7179845b3e1d3084
https://zenn.dev/nenenemo/articles/e10c3caf6dee36
https://zenn.dev/shouta0715/articles/6823ea33cd3778

ビルドを実行

npm run build
Route (app)                              Size     First Load JS
┌ ○ /                                    600 B           120 kB
├ ○ /about                               900 B           100 kB
├ ƒ /api/data                            0 B                0 B
├ ● /post/[id]                           800 B           500 kB
├   ├ /post/1
├   └ /post/2
+ First Load JS shared by all            90 kB
  ├ chunks/abcdef1234567890.js           40 kB
  ├ chunks/1234567890abcdef.js           50 kB
  └ other shared chunks (total)          1 kB

○  (Static)   prerendered as static content
●  (SSG)      prerendered as static HTML (uses getStaticProps)
ƒ  (Dynamic)  server-rendered on demand

First Load JS shared by all

初回読み込み時に必要なJavaScriptファイルのサイズ

○ (Static)

静的コンテンツとして事前レンダリングされたページ

● (SSG)

App Directory構造の場合は実際にはgetStaticPropsで記述していないが、内部的にgetStaticPropsを使用して?静的HTMLとして事前レンダリングされたページ

ƒ (Dynamic)

リクエスト時にサーバーサイド実行される

静的ページ (○, ●) はビルド時に生成され、リクエスト時には即座に配信されるため、パフォーマンスが高く、
動的ページ (ƒ) はリクエストごとにサーバーで生成されるため、内容が常に最新である一方、レンダリングに時間がかかることがあります。

動作環境の確認(Node.js)

Next.jsを使用するためにはNode.jsをインストールする必要があります。

Next.jsは、Node.js上で動作するReactフレームワークであり、Node.jsのランタイム環境で動作します。
そのため、Node.jsがない環境ではNext.jsを実行することはできません。

Homebrewを使用してNode.jsをインストールする場合のコマンドは下記です。

brew install node

Node.jsが正しくインストールされているか現在のバージョンを表示して確認します。
バージョン番号が表示されれば、Node.jsが正常にインストールされています。

node -v

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

? Would you like to customize the default import alias (@/*)?

Yesを選択すると、tsconfig.jsonに下記が追記され、@/*: @をエイリアスとして使用し、@/以降のパスが./src/に対応します。import Input from '@/components/input/input';のように記述することになります。

tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  },
}

オプションを使用して下記のように作成することも可能です。

npx create-next-app@14 <プロジェクト名> --typescript --eslint --src-dir src --app --no-tailwind

下記は対話形式で入力します。

✔ Would you like to customize the default import alias (@/*)? … No / Yes
✔ What import alias would you like configured? … @/*

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"
  }
}

RCをインストールする場合

RC(Release Candidate)版とは、最終的なテスト段階であるバージョンのことです。

npm install next@rc

開発環境を起動

npm run dev

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

開発環境をHTTPSで立ち上げる

HTTPでは、データは暗号化されずに送信されるため、通信経路上で傍受される可能性がありますが、HTTPSでは、データが暗号化されて送信されるため、通信のセキュリティが向上するなどのメリットがあります。
https://nextjs.org/blog/next-13-5
https://vercel.com/guides/access-nextjs-localhost-https-certificate-self-signed

--experimental-httpsフラグをpackage.jsondevスクリプトに追加することで、Next.jsの開発サーバーをHTTPSで起動することができます。

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

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

npm run dev

下記が表示されていると思います。

 ⚠ Self-signed certificates are currently an experimental feature, use at your own risk.
   Attempting to generate self signed certificate. This may prompt for your password
   CA Root certificate created in...

https://x.com/leeerob/status/1743709145386131906

CA Root certificate created in...

証明書の生成が成功し、ルート証明書が作成された場所を示しています。

Vercelにデプロイする

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

下記の手順でGitHubのリポジトリをVercelに連携させます。ちなみにVercelにNext.jsのプロジェクトをデプロイした場合、S3とは異なりサーバーサイドのAPIルートも動作します。これは、Vercelがサーバーレスファンクションをサポートしており、Next.jsのAPIルートがVercelのサーバーレスファンクションとして自動的にデプロイされるためです。

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

Add New...を選択

Projectを選択してください。

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

Deployを選択してください。

Continue to Dashboardを選択

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

apiルートを使用している場合

下記のように他のファイルに記載している情報を読み込もうとするとエラーになり、Vercelでデプロイできません。

src/app/api/microCMS/route.ts
import { NextResponse } from 'next/server';
import { client } from '../../../../libs/client';

export async function GET() {
  try {
    const res = await client.get({
      endpoint: process.env.END_POINT || '',
    });
    return NextResponse.json(res, { status: 200 });
  } catch (error) {
    throw error;
  }
}

https://github.com/vercel/next.js/discussions/32236#discussioncomment-2572290

Vercelにデプロイしたプロジェクトに独自ドメインを紐づける

Setting > Domainsでドメインを入力してAddを押してください。

Recommendedが付いているドメインとそのサブドメインを使うためものを選択して、Addを押してください。

DNS設定

Aレコード、CNAMEレコードが表示されるので、DNS設定を行います。

今回は、Value Domainを使用しています。
DNS情報/URL転送の設定を選択、黒塗りの部分にはIPv4形式のIPアドレスを入力して下の方へスクロールして保存を押してください。

a @ <IPv4形式のIPアドレス>
cname www cname.vercel-dns.com.

ネームサーバーの設定

Nameserversを選択して、Vercelのネームサーバーのアドレス(上の黒塗りの部分)を確認してください。
ちなみに、現在ドメインに設定されているネームサーバーの設定も表示さます。

ネームサーバーの設定に表示されているネームサーバーを入力したら、保存するを押してください。

すぐにサイトにアクセスするとDNS_PROBE_FINISHED_NXDOMAINと表示されますが、しばらく待つと表示されるようになると思います。

ドメインの削除

削除したいドメインのEditを選択、

Removeを押してください。

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>
  );
}

passHref属性

passHref属性を付与しないと、aタグにhref属性が付与されないので必ず記述する。
https://zenn.dev/k_kazukiiiiii/articles/1058a988f3fa5a

型付きルートを有効にする(パスに型補完が効くようにする)

型付きルートを有効にすると、Next.jsがルーティングに関連する型情報を生成し、TypeScriptや他の静的型チェッカーで使用できるようになります。
https://nextjs.org/docs/app/api-reference/next-config-js/typedRoutes

型付きルートを有効にする前

プロジェクトの構成を下記のようにカスタマイズしてください。

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    typedRoutes: true,
  },
};

export default nextConfig;

その後、ローカル環境を立ち上げて型付きルートを有効になっているか確認してください。

npm run dev

下記のようになっていれば問題ないと思います。

   - Experiments (use at your own risk):
     · typedRoutes

型付きルートを有効になっているのがわかると思います。

revalidatePath

特定のページのキャッシュデータを無効化し、そのページを再生成するために使用される関数です。

ISRと関連しており、主に静的に生成されたページで有効で、データの変更があったときに、ページが最新の状態を反映するようにするために利用します。
https://nextjs.org/docs/app/api-reference/functions/revalidatePath

next/navigation(next/routerは非推奨になりました)

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

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

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

import { useRouter } from 'next/navigation';

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

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

  return (
    <button onClick={login}>Log In</button>
  );
}
<button
  onClick={() => {
    router.push(`/blog/${id}`);
  }}
>
詳細
</button>

下記でslugパラメータを取得することができます。

[slug]/page.tsx
  const { slug } = useParams();

https://nextjs.org/docs/app/api-reference/functions/use-params

Not Found(404)

コンテンツが存在しないときに表示されます。

src/app/not-found.tsx
'use client';

import Link from 'next/link';

export default function NotFound() {
  return (
    <div>
      <h2>Not Found</h2>
      <p>Could not find requested resource</p>
      <Link href='/'>Return Home</Link>
    </div>
  );
}

https://nextjs.org/docs/app/api-reference/file-conventions/not-found

エラーページ

エラーが発生しそうなディレクトリに個別でerror.tsxを作成する。
または個別のerror.tsxよりも優先されるsrc/app/global-error.tsxを作成する。
https://nextjs.org/docs/app/building-your-application/routing/error-handling

環境変数 :.env

Next.jsでは、サーバーサイドとクライアントサイドで使用される環境変数を区別しています。
これにより、それぞれの呼び出し方法が異なります。
https://nextjs-ja-translation-docs.vercel.app/docs/basic-features/environment-variables

サーバーサイド

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

クライアントサイド

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

NEXT_PUBLIC_プレフィックスはブラウザに露出するため、AWSの認証情報には使用しないようにしてください。NEXT_PUBLIC_は公開されても安全な環境変数のみに使用し、秘密の情報はサーバーサイドのコードでのみアクセス可能な環境変数に保管してください。

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

API構築

https://zenn.dev/nenenemo/articles/59ca1b03fcf234

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

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 || '';

'use client';使用時の注意点

以下の理由から、親コンポーネントはSSRで、子コンポーネントでCSR('use client';)を使用した方が良いです。

  • SSRのメリットが失われる可能性がある
    "use client"を指定すると、そのコンポーネントはクライアントサイドでのみレンダリングされます。SSRによるサーバーサイドレンダリングのメリット(高速な初期表示やSEOの向上)が失われるため、SSRを活用したい場合は慎重に使用する必要があります。

  • CSRの欠点
    クライアント側でのレンダリングは、JavaScriptが完全にロードされるまでコンテンツが表示されない場合があり、特に低スペックのデバイスやネットワークが遅い環境では、ページの表示が遅くなることがあります。

  • SEOへの影響
    クライアント側のレンダリングは、検索エンジンがコンテンツを正しくインデックス化できない可能性があり、SEOに悪影響を与えることがあります。SSRを活用している場合、サーバーサイドでレンダリングされたHTMLが直接提供されるため、検索エンジンにとって有利な構造になりますが、"use client"を使いすぎるとこれが妨げられます。

クライアントサイドでのインタラクティブな機能やユーザーの入力によって動的に更新されるコンポーネント(たとえば、フォーム、リアルタイム更新、アニメーションなど)は、サーバーサイドレンダリングよりもクライアントサイドで処理する方が適しています。

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';

フォント

Google Fontsのような外部フォントを利用する際に、自動的にフォントの最適化が行われます。これにより、パフォーマンスが向上し、フォントの読み込み時間が短縮されます

まず、@next/font パッケージをプロジェクトにインストールします。

npm install @next/font

https://nextjs.org/docs/pages/building-your-application/optimizing/fonts

src/app/layout.tsx
import type { Metadata } from 'next';
import { Kiwi_Maru } from '@next/font/google';

import './globals.css';

const kiwiMaru = Kiwi_Maru({
  weight: ['400'],
  style: ['normal'],
  subsets: ['latin'],
  preload: true,
  display: 'swap',
  fallback: ['Arial', 'sans-serif'],
  adjustFontFallback: true,
});

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang='en'>
      <body className={`${udpgothic.className}`}>{children}</body>
    </html>
  );
}

weight

weight プロパティは、フォントの太さを指定します。
300:Light
400:Normal
700:Bold

style

フォントのスタイルを指定するためのオプションです。

normal:標準的なフォントスタイル。
italic:イタリックスタイル(右に傾いた文字になるようにデザインされたもの)。
oblique:斜体。

subsets

フォントのサブセットを指定します。
latinは、ラテン文字セットでこれには基本的なA-Zのアルファベット、数字、およびいくつかの特殊文字が含まれているので基本的にはこちらを使用すると思います。

preload

ページの初期ロード時にフォントを優先的に読み込むかどうかを指定します。trueに設定すると、ページのパフォーマンスが向上する可能性があります。

display

フォントの表示方法を指定します。

swap:フォントの読み込み中にフォールバックフォントが表示され、フォントが読み込まれた後にそれが置き換えられるという方法を指します。これにより、ページの読み込みが遅延せず、ユーザーエクスペリエンスが向上します。

fallback

本来のフォントが利用できない場合に使用されるフォールバックフォントの配列を指定します。これにより、フォントの読み込みに失敗した場合のテキストの表示を保証します。

adjustFontFallback

フォントの読み込みが遅延した際にフォントサイズの調整を自動的に行うかどうかを設定します。これにより、ページのレイアウトシフトを最小限に抑えることができます。

フォールバックフォント

Noto Sansがメインフォントなのでそれ以外です。

body {
  font-family: "Noto Sans", Arial, sans-serif;
}

favicon

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

mkdir -p src/app/api/microCMS && touch src/app/api/microCMS/route.ts

https://zenn.dev/nenenemo/articles/59ca1b03fcf234

Image

最適な画像を配信するための仕組み
https://nextjs.org/docs/app/api-reference/components/image

画像のソースファイルのパスを指定する場合

import Image from 'next/image';

<Image
      src="/image.jpg"  
      alt="説明テキスト"          
      width={500}               
      height={500}              
      layout="responsive"
      priority
      style={{ width: '100%', height: 'auto' }}      
    />

ブラウザでコードを確認すると、/_next/imageから始まるパスが指定されています。src="/_next/image?url=%2Flogo-white.png&w=128&q=75"

src

src='logo-white.png'のように記述した場合、下記のエラーが表示されます。先頭にスラッシュ/を付けるか、絶対URL (http:// または https://) にしてください。
Error: Failed to parse src "ファイル名" on next/image, if using relative image it must start with a leading slash "/" or be an absolute URL (http:// or https://)

fill

親要素のサイズに合わせて画像サイズが自動的に調整されます。使用する際は、widthheightの記述は不要です。

sizes

画像の表示サイズを様々なビューポートサイズに応じて調整します。この属性は、特にレスポンシブデザインにおいて有用です。

quality

画像の品質を指定します。この値は1から100までの数値で、JPEGやWebPのような圧縮フォーマットの画像品質を決定します。デフォルトでは75に設定されています。

style

レスポンシブ対応の設定
ブラウザの横幅に合わせて画像が表示されるサイズを変更することができます。

priority

ページロード時に画像が優先的にロードされるようになります。これは特に初期表示エリア(スクロールせずに見える部分)に表示される重要なコンテンツで利用すると良いと思います。
とって、priority属性は、ページのパフォーマンスのLCP(ページの主要コンテンツが読み込まれるまでの時間を計測するパフォーマンス指標)を改善するのに役立ちます。

placeholder

画像がロードされるまでの間に表示されるプレースホルダーのタイプを指定します。

emptyは、画像領域が何も表示されず空のまま、
blurは、これはぼかし効果のプレースホルダーを表示します。このぼかし効果を生成するためには blurDataURLプロパティが必須です。

publicフォルダから画像をimportする

この場合には_nextの下のstatic/mediaフォルダにファイルを作成しているので、width,heightを指定は不要です。

import Image from 'next/image';
import cat from '../public/cat.jpg';

export function CatImage() {
  return <Image src={cat} alt="猫の画像です。" />
}

_next/static/media

ビルド時に生成された最適化された画像を_next/static/mediaフォルダに保存します。これにより、再要求時に最適化処理を繰り返す必要がなく、パフォーマンスが向上します

srcに外部ドメインを指定する場合

next.config.jsにホスト名を設定してください。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'images.microcms-assets.io',
      },
    ],
  },
};

export default nextConfig;

remotePatterns

Next.js14から導入された設定オプションで、<Image />コンポーネントによる外部画像ソースの取り扱いをより詳細に制御するために使用されます。

この設定がない場合、不明なソースからのリソースがページに読み込まれることによる潜在的なセキュリティリスクを避けるためにNext.jsは外部ソースからの画像の読み込みと最適化を拒否します。

特定のパターンに一致するURLからの画像のみを許可することができるので、ドメイン全体を許可するのではなく、より細かいURLパターンを指定することが可能になります。
https://nextjs.org/docs/pages/building-your-application/optimizing/images

next.config.mjs
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 's3.amazonaws.com',
        port: '',
        pathname: '/my-bucket/**',
      },
    ],
  },
};

export default nextConfig;

useSearchParams パラメーター取得

例えば、AmplifyのSSRレンダリングの際は、useSearchParamsを使用しているコンポーネント(またはコンポーネントないに記述してSuspenseで囲む)をSuspenseで囲まないとビルドに失敗します。

import { CheckReserve } from '@/components/check-reserve';
import { Suspense } from 'react';

export default function Page() {
  return (
    <main>
      <Suspense fallback={<div>Loading...</div>}>
        <CheckReserve />  // useSearchParamsを使用しているコンポーネント
      </Suspense>
    </main>
  );
}

https://nextjs.org/docs/app/api-reference/functions/use-search-params

Type 'ReadonlyURLSearchParams' can only be iterated through when using the '--downlevelIteration' flag or with a '--target' of 'es2015' or higher.ts(2802)

tsconfig.json
{
  "compilerOptions": {
    "downlevelIteration": true, // 追加
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

自動画像最適化機能を無効にする

<Image>は、サーバー側で動的に画像をリサイズ、最適化、キャッシュするために、Amazon S3のような静的ファイルサーバーなどに静的ビルドを行ってからアップロードする場合、自動画像最適化機能を無効にしてください。
https://nextjs.org/docs/pages/api-reference/components/image#unoptimized

画像ファイルの扱いは通常の<img>タグと同様になり、ブラウザが直接ファイルを読み込むことになります。

next.config.mjs
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  images: {
    unoptimized: true,
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'images.microcms-assets.io',
      },
    ],
  },
};

export default nextConfig;

lint

https://nextjs.org/docs/app/building-your-application/configuring/eslint

Next.jsのデフォルトのESLint設定(TypeScript)

.eslintrc.json
{
  "extends": ["next/core-web-vitals", "next/typescript"]
}
npm run lint

実行して問題がある場合は問題を表示、下記が表示されれば問題ないです。

format

npm install --save-dev prettier

Prettierの設定ファイルの作成

touch .prettierrc .prettierignore
.prettierrc
{
  "semi": true,
  "singleQuote": true,
  "jsxSingleQuote": true,
  "trailingComma": "es5",
  "printWidth": 100
}

singleQuote

true に設定すると、JavaScript と TypeScript のコードでシングルクォートを使用します。

jsxSingleQuote

true に設定すると、JSX の属性でシングルクォートが使用されます。

.prettierignore
package.json
package-lock.json
.next/
out/
.amplify
amplify_outputs.json
tailwind.config.ts
package.json
 "scripts": {
    "format": "prettier --check ./",
    "format:fix": "prettier --write ./",

フォーマットに問題のあるファイルが表示されます。

npm run format

フォーマットが修正されます。

npm run format:fix

unchangedがついていないファイルが修正されたファイルです。

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

スタイル/CSSの扱い方

基本的にはクラス名はx-iconのように-(ハイフン)を使用する。

CSS Modules

CSSファイル名はpage.module.cssにしてください。

下記のようにスタイルを適用する必要があるので、クラス名はblog-titleのようには記述できず、styles['blog-title']}またはキャメルケースでstyles.blogTitle(基本的にこちらの方が手間がかからず良いと思います。)と記述する必要があります。

src/app/page.tsx
import styles from './page.module.css';

export default function Home() {
  return (
    <>
      <input
        type='text'
        className={styles.blogTitle}
      />
    </>
  );
}

CSS Modulesではグローバルセレクター(bodyidなど)の使用がスタイルの衝突を避けるために制限され、スタイルは主にクラスセレクタを通じて適用されます。
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ファイル(アプリ全体で使用するCSS)に記述する方法があります。

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

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

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

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

複数のクラス名を使用する場合

<main className={`${styles.main} ${styles.secondary}`}>

クラス名がグローバルCSSに定義されている場合

CSSモジュールとグローバルCSSに定義されているクラス名(secondary

<main className={`${styles.main} secondary`}>

globals.css

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

Tailwind CSS

https://tailwindcss.com/docs/installation

テンプレートのパスをを追加してください。

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
 
    // Or if using `src` directory:
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Tailwind ディレクティブをglobals.cssに追加してください。

globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

Tailwind CSSでは、@tailwind base;、@tailwind components;、@tailwind utilities;を使用しているため、HTML要素のデフォルトスタイリングがリセットされることが一般的です。

もしグローバルに適用したいスタイルがある場合は、globals.cssに直接CSSを書くか、extendを使用してテーマをカスタマイズします。

globals.css
@layer base {
  h1 {
    @apply text-3xl font-bold text-blue-500;
  }
}

このように@layer baseを使用して、Tailwindの基本スタイルにカスタムスタイルを追加できます。

CSS-in-JS

CSS-in-JSは、JavaScript内でCSSを定義する方法で、スタイルをコンポーネントに直接組み込むことができます。Next.jsでは、styled-componentsなどのライブラリがよく使用されます。
https://styled-components.com/

簡単に記述すると以下のような記述です。

const styles = {
  border: '2px dashed #cccccc',
};

  return (
    <div style={styles}>
    </div>
  );
};

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

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の開発サーバー(npm run dev)を実行している際にターミナルで表示される下記のログは、サーバーがリクエストを処理している状態や結果を示しているので、ここから確認することができます。

下記の場合

route.js
import { NextRequest, NextResponse } from 'next/server';
import OpenAI from 'openai';

export async function POST(req: NextRequest) {
  const { messages } = await req.json();

  console.log('messages', messages);

// 以下は省略

このようにデバッグできます。

"use server"

クライアントサイドのフォームの送信やボタンクリックなどのイベントからサーバーサイドで実行される関数を呼び出すことができます。
https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations

データベースのクエリや外部APIの呼び出しなどの非同期処理が必要な場合は、asyncawaitを使用してください。

src/app/page.tsx
'use client';
import { serverTest } from './actions/serverTest';

export default function Home() {
 const handleClick = () => {
    try {
      const result = serverTest('テスト');
      console.log('サーバーからの応答:', result);
    } catch (error) {
      console.error('エラーが発生しました:', error);
    }
  };

  return (
    <button onClick={handleClick}>
      送信
    </button>
  );
}

'use server'を使用する関数は必ずasyncを使用して非同期関数として定義する必要があります。使用していない場合には、× Server actions must be async functionsと表示されます。

src/app/actions/serverTest.js
'use server';

export function serverTest(data) {
  console.log('データ:', data);
  return { success: true, message: 'データが正常に処理されました' };
}

クライアントサイドのボタンクリックイベントからサーバーサイド関数を呼び出すことができています。

ちなみにNext-ActionヘッダのIDがクライアントサイドで確認できるので、Next-Actionヘッダーを含むリクエストを送信すると誰でもリクエストできてしまいます。外部からの不正リクエストから保護してください。

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`}
/>

モジュールパスの設定

importCannot find module '@/と表示された場合はモジュールパスの設定をしてください。
デフォルトでは、"@/*": ["./src/*"]しか記載されていないので、追記する必要があります。

今回は、utils以下を追記します。

tsconfig.json
{
    "paths": {
      "@/*": ["./src/*"],
      "@/utils/*": ["./utils/*"]
    }
}

[静的]OGP画像の設定

ページごとに下記を作成する。
opengraph-image.jpg
opengraph-image.alt.txt
twitter-image.jpg
twitter-image.alt.txt

src/app/layout.tsx
export const metadata: Metadata = {
  metadataBase: new URL('https://your-domain.com/'), // 本番環境

  title: 'Create Next App',
  description: 'Generated by create next app',
  openGraph: {
    type: 'website',
    title: 'タイトル',
    description: '説明',
  },
  twitter: {
    title: 'twitterタイトル',
    description: 'twitter説明',
    card: 'summary_large_image',
  },
};
}

https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image

[動的]OGPを動的に生成

https://nextjs.org/docs/app/api-reference/functions/image-response

opengraph-image.alt.txtは作成せずに、同じファイル内に記述できます。

src/app/opengraph-image.tsx
import { ImageResponse } from 'next/og';

export const runtime = 'edge';

export const alt = 'About Acme';
export const size = {
  width: 1200,
  height: 630,
};

export const contentType = 'image/png';

export default async function Image() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        OGP
      </div>
    ),
    {
      ...size,
    }
  );
}

名前付きエクスポート(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';

非推奨(getStaticPropsgetStaticPathsexportPathMap

App Directory構造では、従来のpagesディレクトリ内で行っていたgetStaticPropsgetStaticPathsnext.config.mjsexportPathMapのようなデータフェッチングメソッドの使用が非推奨になっています。

generateStaticParamsを使用してください。

generateStaticParams

https://nextjs.org/docs/app/api-reference/functions/generate-static-params

query

/productページにidというクエリパラメータを追加することができます。

コンポーネントの呼び出しについて

サーバーコンポーネントコンポーネントからクライアントコンポーネント('use client';)を呼び出す場合

サーバー側で処理した後、クライアントに結果を送信するため、エラーは発生しません。

クライアントコンポーネント('use client';)からサーバーコンポーネントを呼び出す場合

クライアントサイドでサーバーの機能を呼び出すため、実行環境が不足しているためエラーが発生します。

ガイドライン

変数名

ハイフン(-)は変数名に使用できないので、キャメルケース(authenticationCode)を使用する。

命名規則

ケバブケース(単語をハイフンで区切る方式)を使用する。
client-api.ts
https://qiita.com/hironori_narita/items/4b06db0953053d41c4a0

各ディレクトリの場所

public

静的ファイル(画像、CSS、JavaScriptなど)

data

アプリケーションのデータファイルや設定ファイルなど、アプリケーションの内部で使用するファイル
このディレクトリの内容は、外部から直接アクセスされることはありません。

componentsディレクトリ

appと同じディレクトリに作成する(src/components)。
コンポーネントごとのフォルダを作成する。

命名規則の例button.tsx

libsディレクトリ

ルートディレクトリに作成する。
特に外部APIとのインターフェースやデータベースの操作、またはビジネスロジックを含むモジュールを格納するために使用されます。libディレクトリ内のコードは、しばしばアプリケーションの核心的な機能やサービスを形成します。

例:
database.js:データベース接続と操作を扱う。
apiClient.js:外部APIへのリクエストを管理する。(AWSクライアントなど)
paymentProcessing.js:決済処理を担う。

utilsディレクトリ

ルートディレクトリに作成する。
汎用的で、どんなプロジェクトにも適用可能な関数など。

例:
formatDate.js:日付をフォーマットする関数。
calculateTax.js:税金を計算する関数。
capitalize.js:文字列の最初の文字を大文字にする関数。

hooks

ルートディレクトリに作成する。
Reactのカスタムフックを格納するために使用されます。Reactフックは、関数コンポーネント内で状態やライフサイクルなどのReactの機能を「フックする」ための機能です。カスタムフックは、複数のコンポーネント間でロジックを再利用可能にするために使用されます。


useAuth:認証状態を管理するためのフック。
useForm:フォーム入力とそのバリデーションを扱うフック。
useFetch:APIからデータを取得するためのフック。

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

Error: parameter is required (check serviceDomain and apiKey)

VercelでNext.jsのプロジェクトをデプロイする際に生じたこのエラーは、主にAPI接続設定の問題で必要なパラメータが提供されていないために起きています。

ローカル開発環境での動作が正常であった場合、同じ環境変数がVercelのプロジェクト設定にも適切に設定されているか確認してください。Vercelのダッシュボードから、Settings > Environment Variablesで設定できます。

Error: Page "/api/auth/[...nextauth]" is missing "generateStaticParams()" so it cannot be used with "output: export" config.

src/app/api/auth/[...nextauth]/route.tsというディレクトリ構成の場合に静的サイト生成をしようとした場合に出るエラーだと思います。

src/app/api/auth/route.tsと言う構成に変更すると表示されなくなります。

終わりに

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

Discussion