Next.jsでよく使う内容をまとめてみました
はじめに
基本的にはApp Router構造の場合で記述しています。
Next.jsとは
Vercelによって開発されているReactベースのフレームワークです。
クライアントサイドレンダリング(CSR)、SSR(サーバーサイドレンダリング)、SSG(静的サイト生成)、ISR(インクリメンタルスタティックリジェネレーション)が含まれ、SPA(シングルページアプリケーション)の開発もサポートしています。Next.jsを使用することで、これらの異なる手法をプロジェクトのニーズに応じて選択し、組み合わせることができます。
Reactとは
下記にまとめています。
バージョンごとのルーティング機能
Pages Router(v12以前)
Next.jsのバージョン12まで使用されていたルーティング機能で、/pages
フォルダ直下に配置されたファイルが自動的に各ページに変換されるシステムです。この方式では、ファイルのパスがURLのパスと直接対応しています。
API Routes(v12以前)
Pages Routerの一部で、/pages/api
フォルダに配置されたJavaScriptファイルがAPIエンドポイントとして機能する仕組みです。これにより、Next.jsアプリケーションがサーバーサイドのAPIとしても動作できるようになっています。
v12.2以前
以前はリクエストがNextApiRequest
型、レスポンスがNextApiResponse
型でしたが、それぞれNextRequest
とNextResponse
に型が変更されています。
NextResponse
基本的にはResponse()
と同じです。
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;
}
}
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では、ビルドする際にプロジェクト内の各ページについてどのレンダリング方法が用いられているかを示すログを出力することができます。
ビルドを実行
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)をサポートしているため、コードの変更が即座にブラウザに反映されます。
プロジェクトの作成
デフォルトで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
? Would you like to customize the default import alias (@/*)?
Yesを選択すると、tsconfig.jsonに下記が追記され、@/*: @
をエイリアスとして使用し、@/
以降のパスが./src/
に対応します。import Input from '@/components/input/input';
のように記述することになります。
{
"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には以下が追記されています。
{
{
"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では、データが暗号化されて送信されるため、通信のセキュリティが向上するなどのメリットがあります。
--experimental-https
フラグをpackage.json
のdev
スクリプトに追加することで、Next.jsの開発サーバーをHTTPSで起動することができます。
"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...
CA Root certificate created in...
証明書の生成が成功し、ルート証明書が作成された場所を示しています。
Vercelにデプロイする
GitHubのリポジトリを作成は済ませておいてください。
下記の手順でGitHubのリポジトリをVercelに連携させます。ちなみにVercelにNext.jsのプロジェクトをデプロイした場合、S3とは異なりサーバーサイドのAPIルートも動作します。これは、Vercelがサーバーレスファンクションをサポートしており、Next.jsのAPIルートがVercelのサーバーレスファンクションとして自動的にデプロイされるためです。
Vercelにログインしてください。
Add New...
を選択
Project
を選択してください。
デプロイしたいリポジトリのImport
を選択
Deploy
を選択してください。
Continue to Dashboard
を選択
Visit
を選択すると、デプロイされたページにアクセスできます。
apiルートを使用している場合
下記のように他のファイルに記載している情報を読み込もうとするとエラーになり、Vercelでデプロイできません。
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;
}
}
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
属性が付与されないので必ず記述する。
型付きルートを有効にする(パスに型補完が効くようにする)
型付きルートを有効にすると、Next.jsがルーティングに関連する型情報を生成し、TypeScriptや他の静的型チェッカーで使用できるようになります。
型付きルートを有効にする前
プロジェクトの構成を下記のようにカスタマイズしてください。
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
typedRoutes: true,
},
};
export default nextConfig;
その後、ローカル環境を立ち上げて型付きルートを有効になっているか確認してください。
npm run dev
下記のようになっていれば問題ないと思います。
- Experiments (use at your own risk):
· typedRoutes
型付きルートを有効になっているのがわかると思います。
revalidatePath
特定のページのキャッシュデータを無効化し、そのページを再生成するために使用される関数です。
ISRと関連しており、主に静的に生成されたページで有効で、データの変更があったときに、ページが最新の状態を反映するようにするために利用します。
next/navigation(next/routerは非推奨になりました)
ユーザーのアクションやアプリケーションの状態に基づいて動的にページ遷移を行いたい場合に使います。例えば、フォームの送信後にユーザーを特定のページにリダイレクトしたり、特定の条件下で自動的にユーザーを異なるページに遷移させる場合などです。
next/navigation
は、next/link
とは異なり、自動的にプリフェッチングを行わず、ページ遷移をトリガーすると、その時点で必要なリソースの読み込みが始まります。
useRouter
フックを通じてアクセスでき、push
やreplace
などのメソッドを提供し、コード内から直接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
パラメータを取得することができます。
const { slug } = useParams();
Not Found(404)
コンテンツが存在しないときに表示されます。
'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>
);
}
エラーページ
エラーが発生しそうなディレクトリに個別でerror.tsx
を作成する。
または個別のerror.tsx
よりも優先されるsrc/app/global-error.tsx
を作成する。
環境変数 :.env
Next.jsでは、サーバーサイドとクライアントサイドで使用される環境変数を区別しています。
これにより、それぞれの呼び出し方法が異なります。
サーバーサイド
ENDPOINT='https://example.com/'
const endPoint = process.env.ENDPOINT || '';
クライアントサイド
環境変数を使用する('use client';
など)には、環境変数名をNEXT_PUBLIC_
で始める必要があります。
NEXT_PUBLIC_
プレフィックスはブラウザに露出するため、AWSの認証情報には使用しないようにしてください。NEXT_PUBLIC_
は公開されても安全な環境変数のみに使用し、秘密の情報はサーバーサイドのコードでのみアクセス可能な環境変数に保管してください。
NEXT_PUBLIC_ENDPOINT='https://example.com/'
const endPoint = process.env.NEXT_PUBLIC_ENDPOINT || '';
API構築
静的ファイルの生成を行う
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_
で始まるものに限られます。
上記の理由から次のように記述して使用してください。
NEXT_PUBLIC_ENDPOINT='https://example.com/'
.gitignoreファイルにも.env
を追記
.env
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()
コンストラクタやユーティリティ関数を使ってレスポンスを生成する方法に変更されています。
useRouter
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先を修正してください。
import { useRouter } from 'next/navigation';
フォント
Google Fontsのような外部フォントを利用する際に、自動的にフォントの最適化が行われます。これにより、パフォーマンスが向上し、フォントの読み込み時間が短縮されます
まず、@next/font パッケージをプロジェクトにインストールします。
npm install @next/font
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
というファイル名で作成するとローカルでも反映されます。
mkdir -p src/app/api/microCMS && touch src/app/api/microCMS/route.ts
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
親要素のサイズに合わせて画像サイズが自動的に調整されます。使用する際は、width
とheight
の記述は不要です。
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
にホスト名を設定してください。
/** @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パターンを指定することが可能になります。
// 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>
);
}
Type 'ReadonlyURLSearchParams' can only be iterated through when using the '--downlevelIteration' flag or with a '--target' of 'es2015' or higher.ts(2802)
{
"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のような静的ファイルサーバーなどに静的ビルドを行ってからアップロードする場合、自動画像最適化機能を無効にしてください。
画像ファイルの扱いは通常の<img>
タグと同様になり、ブラウザが直接ファイルを読み込むことになります。
// 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
Next.jsのデフォルトのESLint設定(TypeScript)
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
npm run lint
実行して問題がある場合は問題を表示、下記が表示されれば問題ないです。
format
npm install --save-dev prettier
Prettierの設定ファイルの作成
touch .prettierrc .prettierignore
{
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"trailingComma": "es5",
"printWidth": 100
}
singleQuote
true に設定すると、JavaScript と TypeScript のコードでシングルクォートを使用します。
jsxSingleQuote
true に設定すると、JSX の属性でシングルクォートが使用されます。
package.json
package-lock.json
.next/
out/
.amplify
amplify_outputs.json
tailwind.config.ts
"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>コンポーネントを使用することが推奨されています。
@next/next/no-img-elementのエラー
例えばS3にデプロイする場合などにnext/imageを使用しているとうまく表示されないので、<img> タグを使用するようにしました。
<img> タグを使用すると@next/next/no-img-element
のエラーがエディター上に表示されるのでESLintの設定ファイルに追記します。
{
"extends": "next/core-web-vitals",
"rules": { //追記
"@next/next/no-img-element": "off" //追記
}
}
スタイル/CSSの扱い方
基本的にはクラス名はx-icon
のように-
(ハイフン)を使用する。
CSS Modules
CSSファイル名はpage.module.css
にしてください。
下記のようにスタイルを適用する必要があるので、クラス名はblog-title
のようには記述できず、styles['blog-title']}
またはキャメルケースでstyles.blogTitle
(基本的にこちらの方が手間がかからず良いと思います。)と記述する必要があります。
import styles from './page.module.css';
export default function Home() {
return (
<>
<input
type='text'
className={styles.blogTitle}
/>
</>
);
}
CSS Modulesではグローバルセレクター(body
やid
など)の使用がスタイルの衝突を避けるために制限され、スタイルは主にクラスセレクタを通じて適用されます。
指定した場合は下記のようなエラーが出ます。
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
テンプレートのパスをを追加してください。
/** @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
に追加してください。
@tailwind base;
@tailwind components;
@tailwind utilities;
Tailwind CSSでは、@tailwind base;、@tailwind components;、@tailwind utilities;
を使用しているため、HTML要素のデフォルトスタイリングがリセットされることが一般的です。
もしグローバルに適用したいスタイルがある場合は、globals.css
に直接CSSを書くか、extend
を使用してテーマをカスタマイズします。
@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
などのライブラリがよく使用されます。
簡単に記述すると以下のような記述です。
const styles = {
border: '2px dashed #cccccc',
};
return (
<div style={styles}>
</div>
);
};
末尾のスラッシュを有効にする
trailingSlashの設定を追加してください。
設定を有効にすることで設定前は、/about/
は/about
にリダイレクトされていますが、/about
のようなURLは/about/
へリダイレクトされるようになります。
module.exports = {
trailingSlash: true, //追記
}
サーバーサイドのデバッグ情報を含むログを確認
Next.jsの開発サーバー(npm run dev
)を実行している際にターミナルで表示される下記のログは、サーバーがリクエストを処理している状態や結果を示しているので、ここから確認することができます。
下記の場合
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"
クライアントサイドのフォームの送信やボタンクリックなどのイベントからサーバーサイドで実行される関数を呼び出すことができます。
データベースのクエリや外部APIの呼び出しなどの非同期処理が必要な場合は、async
とawait
を使用してください。
'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
と表示されます。
'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
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
の呼び出しなどで使用すると思います。
.env.developmentと.env.productionの.envファイルを作成します。
それぞれの環境変数は下記の場合に使用されます。これを使用して環境によってパスを変更します。
.env.development
はnpm run dev
実施時(ローカル環境)
.env.production
はnpm run build
実施時(本番=静的な生成を行った場合)
.それぞれの.envファイルの記述
NEXT_PUBLIC_BASE_PATH =/
BASE_PATH =/
NEXT_PUBLIC_BASE_PATH =/subdirectory-name
BASE_PATH =/subdirectory-name
使用例
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
<img
src={`${basePath}/images/icon.png`}
/>
モジュールパスの設定
import
でCannot find module '@/
と表示された場合はモジュールパスの設定をしてください。
デフォルトでは、"@/*": ["./src/*"]
しか記載されていないので、追記する必要があります。
今回は、utils
以下を追記します。
{
"paths": {
"@/*": ["./src/*"],
"@/utils/*": ["./utils/*"]
}
}
[静的]OGP画像の設定
ページごとに下記を作成する。
opengraph-image.jpg
opengraph-image.alt.txt
twitter-image.jpg
twitter-image.alt.txt
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',
},
};
}
[動的]OGPを動的に生成
opengraph-image.alt.txt
は作成せずに、同じファイル内に記述できます。
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';
getStaticProps
、getStaticPaths
、exportPathMap
)
非推奨(App Directory構造では、従来のpages
ディレクトリ内で行っていたgetStaticProps
、getStaticPaths
やnext.config.mjs
のexportPathMap
のようなデータフェッチングメソッドの使用が非推奨になっています。
generateStaticParamsを使用してください。
generateStaticParams
query
/product
ページにid
というクエリパラメータを追加することができます。
コンポーネントの呼び出しについて
'use client';
)を呼び出す場合
サーバーコンポーネントコンポーネントからクライアントコンポーネント(サーバー側で処理した後、クライアントに結果を送信するため、エラーは発生しません。
'use client';
)からサーバーコンポーネントを呼び出す場合
クライアントコンポーネント(クライアントサイドでサーバーの機能を呼び出すため、実行環境が不足しているためエラーが発生します。
ガイドライン
変数名
ハイフン(-
)は変数名に使用できないので、キャメルケース(authenticationCode
)を使用する。
命名規則
ケバブケース(単語をハイフンで区切る方式)を使用する。
client-api.ts
各ディレクトリの場所
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 に明示的に指示すると解決すると思います。
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