Next.jsの勉強がてら公式のチュートリアルを1からなぞってみました。
実際にチュートリアルをベースに書いたソースコードはこちら
- Next.js基礎コース App Router やってみた 1 ~ 3
- Next.js基礎コース App Router やってみた 4 ~ 6
- Next.js基礎コース App Router やってみた 7 ~ 9
- Next.js基礎コース App Router やってみた 10 ~ 12
- Next.js基礎コース App Router やってみた 13 ~ 15
- Next.js基礎コース App Router やってみた 16 ~ 17
1. Getting Started
npm install -g pnpm
# create-next-app@latest next.jsのプロジェクト作成用CLI
# nextjs-dashboard プロジェクトのディレクトリ名
# --example サンプルプロジェクトをテンプレートにする指定
# --use-pnpm パッケージマネージャーとしてpnpmを使う指定
npx create-next-app@latest nextjs-dashboard \
--example "https://github.com/vercel/next-learn/tree/main/dashboard/starter-example" \
--use-pnpm
cd nextjs-dashboard
ディレクトリ構成
-
app/
アプリケーションのすべてのルート、コンポーネント、ロジックが含まれており、殆どの作業はここで行う-
lib/
再利用可能なユーティリティ関数やデータ取得関数など。 -
ui/
カード、テーブル、フォームなど、アプリケーションのUIコンポーネント。
-
-
/public
画像など、静的アセット -
next.config.ts
設定ファイル
├── app
│ ├── layout.tsx
│ ├── lib
│ │ ├── data.ts
│ │ ├── definitions.ts // データベースから返される型の定義
│ │ ├── placeholder-data.ts // プレースホルダデータ
│ │ └── utils.ts
│ ├── page.tsx
│ ├── query
│ │ └── route.ts
│ ├── seed
│ │ └── route.ts
│ └── ui
│ ├── acme-logo.tsx
│ ├── button.tsx
│ ├── customers
│ │ └── table.tsx
│ ├── dashboard
│ │ ├── cards.tsx
│ │ ├── latest-invoices.tsx
│ │ ├── nav-links.tsx
│ │ ├── revenue-chart.tsx
│ │ └── sidenav.tsx
│ ├── global.css
│ ├── invoices
│ │ ├── breadcrumbs.tsx
│ │ ├── buttons.tsx
│ │ ├── create-form.tsx
│ │ ├── edit-form.tsx
│ │ ├── pagination.tsx
│ │ ├── status.tsx
│ │ └── table.tsx
│ ├── login-form.tsx
│ ├── search.tsx
│ └── skeletons.tsx
├── next.config.ts
├── next-env.d.ts
├── node_modules
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
│ ├── customers
│ │ ├── amy-burns.png
│ │ ├── balazs-orban.png
│ │ ├── delba-de-oliveira.png
│ │ ├── evil-rabbit.png
│ │ ├── lee-robinson.png
│ │ └── michael-novotny.png
│ ├── favicon.ico
│ ├── hero-desktop.png
│ ├── hero-mobile.png
│ └── opengraph-image.png
├── README.md
├── tailwind.config.ts
└── tsconfig.json
サーバーの起動
pnpm i
# 開発サーバーの起動
pnpm dev
# 本番環境の起動
phpm build
phpm start
ファイルシステム規約
| ファイル名/ディレクトリ | 用途 / 意味 |
|---|---|
page.js |
そのフォルダが対応する ページコンポーネント(URL にマッピングされる)を定義。ルート URL (例: /about)なら app/about/page.js。 |
layout.js |
子ルートを包む レイアウト コンポーネント。共通の UI(ヘッダー、サイドバーなど)を定義。ネストも可能。 |
loading.js |
ページ読み込み中に出すローディング UI を定義。遷移中などに表示される。 |
error.js |
そのセグメント配下で発生した例外をキャッチして表示するエラーページ。 |
not-found.js |
そのセグメント(パス)にマッチするリソースがない場合の “404 相当” を表示。 |
route.js |
API ルートハンドラー(HTTP リクエストを処理するエンドポイントとして使える)を定義。 |
template.js |
レイアウトとは異なるテンプレート構造で、レイアウトとページを分けて定義したい場合に使うファイル。 |
default.js |
レイアウトやページで特定ファイルがない場合にデフォルト振る舞いを定義できる。 |
forbidden.js / unauthorized.js
|
アクセス権限がないときのレスポンス用ページを定義できる。 |
instrumentation.js / instrumentation-client.js
|
アプリのパフォーマンス/モニタリングに関する処理を追加したいとき用。 |
public/ |
静的ファイル(画像、favicon、robots.txt など)を置く場所。これらは URL でそのまま参照される(例: /favicon.ico)。 |
[hoge] |
角括弧 [...] を使って動的セグメントを定義できる([id]/page.js -> /123 にマッチ)。[...slug] のような形で多階層を一括キャッチ可能。 |
2. CSSスタイル
/app/ui/global.css でアプリケーション内のすべてのルートにCSSルールを適用します。
/app/layout.tsx ファイルで /app/ui/global.css をインポートすることでアプリケーションにグローバルスタイルを追加できます。
/app/layout.tsx
import '@/app/ui/global.css'; // 追加
export default function RootLayout({ children }: { children: React.ReactNode; }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
NOTE: import の
@はプロジェクトのルートを指します。
この設定はtsconfig.jsonに記述されています。{ "compilerOptions": { "baseUrl": ".", // モジュール解決の基準ディレクトリを指定 "paths": { // keyがインポート側の書き方、valueが実際のファイルパス "@/*": ["./*"] // @/xxx と書いたら ./xxx を見に行け } } }
/app/ui/global.css にはほとんど記述がないにも関わらず、結構リッチなスタイルがつくのは、以下の定義のおかげです。
/app/ui/global.css
@tailwind base;
@tailwind components;
@tailwind utilities;
Tailwindcss
Tailwindcssを使うと、以下のようにクラス名ベースでスタイルをつけられます。
<h1 className="text-blue-500">I'm blue!</h1>
/app/page.tsx を確認すると要素の className にtailwindのクラスが使用されていることがわかります。
CSSモジュール
CSSモジュールを使用すると一意のクラス名を自動生成してCSSをコンポーネントにスコープできるため、衝突の心配がありません。
/app/ui/home.module.css
.shape {
height: 0;
width: 0;
border-bottom: 30px solid black;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
}
/app/page.tsx
import AcmeLogo from '@/app/ui/acme-logo';
import { ArrowRightIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
import styles from '@/app/ui/home.module.css'; // CSSモジュールのインポート
export default function Page() {
return (
<main className="flex min-h-screen flex-col p-6">
<div className={styles.shape} /> {/* CSSモジュールで定義したshapeクラスを使用 */}
// ...
)
}
clsx ライブラリでクラス名を切り替える
要素の状態やその他の条件に基づいて、スタイルを適用したり除外したりする場合に利用します。
clsx はクラス名を簡単に切り替えられるライブラリです。
function sample({ status }: {status: boolean}) {
return (
<div className={clsx(
'px-2 py-1', // 共通クラス
{ // 条件付きクラス「クラス名:条件」の形式になっており、条件がtrueのクラスが適用される
'block': status === true,
'hidden': status === false,
}
)} />
)
}
3. フォントと画像の最適化
フォントの最適化
ブラウザが最初にシステムフォントでテキストレンダリングし、その後カスタムフォントに置き換えることでレイアウトシフトが発生しますが、 Next.jsではビルド時にフォントファイルをダウンロードし静的アセットとともにホストすることでパフォーマンスに影響を与えるようなフォントの追加ネットワークリクエストは発生しません。
プライマリフォントの追加
カスタムGoogleフォントをアプリケーションに追加してみます。
アプリケーション全体で使用するフォントを定義する /app/ui/fonts.ts を作成します。
next/font/google モジュールから Inter フォントをインポートします。
サブセット には 'latin' を指定します。
/app/ui/fonts.ts
// next/font/google は Google Fonts を直接使うための Next.js 組み込みモジュール。
// Inter は Google Fonts に登録されている、UI 向けに設計されたモダンなサンセリフ体(ゴシック体)フォント
import { Inter } from 'next/font/google';
// どの文字セット(subsets)を使うかを指定
// latin(英語・西欧言語)
// latin-ext(中央ヨーロッパ言語)
// japanese(Noto Sans JP など)
export const inter = Inter({ subsets: ['latin'] });
/app/layout.tsx
import '@/app/ui/global.css';
import { inter } from '@/app/ui/fonts'; // 追加
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
{/* antialiased Tailwindのフォントを滑らかにするクラス */}
<body className={`${inter.className} antialiased`}>{children}</body>
</html>
);
}
セカンダリフォントの追加
/app/ui/fonts.ts
// ... 略 ...
export const lusitana = Lusitana({ subsets: ['latin'], weight: ['400', '700'] });
/app/page.tsx
// ... 略 ...
import { lusitana } from '@/app/ui/fonts';
export default function Page() {
return (
<main className="flex min-h-screen flex-col p-6">
<div className="flex h-20 shrink-0 items-end rounded-lg bg-blue-500 p-4 md:h-52">
<AcmeLogo /> {/* コメントイン */}
</div>
{/* ... 略 ... */}
{/* lusitanaフォントのクラス名を追加 */}
<p className={`${lusitana.className} text-xl text-gray-800 md:text-3xl md:leading-normal`}>
{/* ... 略 ... */}
</p>
{/* ... 略 ... */}
</main>
);
}
画像の最適化
Next.jsは、画像などの静的アセットを /public から提供します。/public 内のファイルはアプリケーション内で参照可能です。
<Image> コンポーネント
<Image> コンポーネントはHTMLの <img> の拡張であり次のような最適化機能が備わっています。
- 画像読み込み時にレイアウトシフトが発生しないようにする
- viewportの小さいデバイスに大きな画像が送信されないように、画像サイズを変更する
- デフォルトで画像を遅延読込する (viewportに入ると読み込まれます)
- ブラウザが対応している場合、WebPやAVIFなどの最新形式で画像を提供する
/app/page.tsx
// ...
import Image from 'next/image'; // 追加
export default function Page() {
return (
<main className="flex min-h-screen flex-col p-6">
<div>
{/* ... */}
<div className="flex items-center justify-center p-6 md:w-3/5 md:px-28 md:py-12">
{/* デスクトップ用 */}
<Image
src="/hero-desktop.png"
width={1000}
height={760}
className="hidden md:block"
alt="Screenshots of the dashboard project showing desktop version"
/>
{/* モバイル用 */}
<Image
src="/hero-mobile.png"
width={560}
height={620}
className="block md:hidden"
alt="Screenshots of the dashboard project showing mobile version"
/>
</div>
</div>
</main>
);
}
width height を設定しておくことで、レイアウト連れを防ぐことができます。これらの指定はソース画像と同じアスペクト比に合わせます。
モバイル画面では画像がDOMから削除されるように hidden クラスが、デスクトップ画面では画像が表示されるように md:block クラスが設定されています。