1 連絡
- 課題1~
ぼくのかんがえたさいきょうのTodoアプリ ~ の提出期限は「11月19日 (水)
23時」です。
- 提出先 : Teams「PG3-課題1」
- 課題1は、ポートフォリオ (自己紹介と作品集) として作成したページからも リンクを貼っておくように してください。
- その他、ポートフォリオには 今年度の文化祭展示の概要
(スクリーンショットや会場写真があると説得力が高くなります)
や、他科目で開発したソフトウエアや修得したスキルなども、(記憶が鮮明なうちに)
こまめに追記するようにしてください。
- 特に、文化祭展示などは チームプロジェクトでの開発の経験があること のアピールができるので必ず記載してください。
- 次回の講義で 小テスト❸ を実施します。主に、今回の講義内容からの出題となります。
2 バックエンド処理を含めたブログアプリの開発
ここまでの授業では、フロントエンドのみで完結する Todoアプリの開発 を通じて、「React」を使ったモダンウェブ開発の基礎について学んできました。ここからの授業では Next.js というフレームワークを使用し バックエンド (サーバサイド) を含めた「本格的なモダンウェブアプリ開発」 について体験的に学んでいきます。
現代のウェブアプリは「❶ ウェブブラウザで実行されるフロントエンド部分」と「❷ サーバ上で実行されるバックエンド部分」を RESTfulなウェブAPI (主にHTTP/HTTPSプロトコル上で実装) を介して「疎結合」な形で構成することが一般的となっています。そして、この設計によってフロントエンドとバックエンドで 異なる「プログラミング言語」や「フレームワーク」を使用すること が可能になっています。
フロントエンドの開発言語 は実質的に HTML/CSS/JavaScript (TypeScript) に限定されており、React や Vue.js、Svelte などのコンポーネント指向フレームワークが主流となっています。かつて広く使用されていた jQuery は、これらのモダンフレームワークの登場と普及によって、新規開発案件での採用は減少傾向 にあります。
一方、バックエンドにおいては、次のような多様な「プログラミング言語」と「フレームワーク」の組み合わせの選択肢があり、実際に幅広く活用されています。
- Ruby (Ruby on Rails)
- PHP (Laravel)
- Java (Spring Boot)
- Python (Django、FastAPI、Flask)
- C# (.NET ASP Core)
- Rust (Actix)
- Go (Gin、Echo)
- JavaScript/TypeScript (Express、Fastify)
このような状況のなか「Next.js」は、TypeScript (JavaScript) を用いて フロントエンドとバックエンドを単一のプロジェクトとして統合的に開発できるという異色 ( 革新的?! ) のフレームワーク として人気を集めています。Next.js のフロントエンドは Reactをベースとしているため (ただしルーティングなどの一部機能を除く)、Todoアプリ開発で学んだ React のスキルと経験をそのまま活かして開発することができます。
今回の授業からは、この Next.js を使用して「ブログアプリ」の開発に取り組んでいきます(チュートリアル的に「ブログアプリ」の開発を進めていきます)。最終的にはSupabaseという Baas (Backend As A Service) を活用し、ブログ記事の CRUD操作 ( Create/Read/Update/Delete )、ログイン機能 (ユーザ認証機能) までを実装していきます。
ブログアプリの完成後は、最終課題 として「オリジナルのウェブアプリを開発」に取り組んでもらいます。
大まかな流れは、次のようになっています。
- Next.js 開発環境の構築 ← 今回の授業
- ブログアプリの「フロントエンド開発」のチュートリアル ←
今回の授業
- microCMS の利用
- ブログアプリの「バックエンド開発」のチュートリアル
- ブログアプリのカスタマイズとつくり込み → 課題2
- オリジナルのウェブアプリ → 課題3 (最終課題)
2.0.1 定着確認
- Next.js
がフレームワークとしてカバーする範囲は「フロントエンド」、「バックエンド」、「フロントエンドとバックエンドの両方」のどれか。
- 答え: フロントエンドとバックエンドの両方
- Typescript (JavaScript) 以外で、バックエンド開発に使用される (代表的な) プログラミング言語とフレームワークの組み合わせを2組答えよ。
- DBと連携するウェブアプリ開発の文脈で「CRUD操作」の各文字は何の略語か答えよ。
- 答え: Create/Read/Update/Delete
- RESTful APIにおいて採用される代表的な通信プロトコルを挙げよ。
- 答え: HTTP/HTTPS。その他、CoAP や WSS などが採用されることがある。
- jQuery は、フロントエンド用のライブラリか、バックエンド用のライブラリか答えよ。
- 答え: フロントエンド用のライブラリ
3 Next.js の 開発環境構築
Next.js は 2025年10月21日に最新バージョンの「16」がリリースされています。しかしながら、2025年11月3日現在、主要な周辺ライブラリが、まだ Next.js 16 に対応していない状況となっています。そのため、本授業では安定版の Next.js 15 を使用していきます。
以下、プロジェクト名称を next-blog-app として、Next.js 15
の開発環境を構築していきます。
ターミナル (PowerShell) を起動し、プロジェクトフォルダを作成したい位置
(例えば C:\Users\xxxx\Documents\
など。ただし、OneDrive同期が適用されていないフォルダ) に移動してください。Next.js
の開発環境は、次のコマンドを使って構築していきます。
npx create-next-app@15
ここで npm ではなく npx
とする点に注意してください (違いが気になる人は生成AIで解決してください)。また、最新バージョン
である「Next.js 16」で環境構築したい場合は
npx create-next-app@latest としてください。
(プロンプト例)
Node.js における
npmとnpxの違いを初心者向けに分かりやすく解説してください。
はじめて npx create-next-app
を実行するときや、時間を空けて実行するときは、以下のように訊かれるので y
で回答してください。
Need to install the following packages:
create-next-app@15.5.6
Ok to proceed? (y)
create-next-app
では、開発環境に関していくつかの質問がされます。ここでは、以下のように回答 (設定)
してください。ここで誤った設定をしてしまうと、あとからの修正が大変になるため、慎重に設定してください。
√ What is your project named? … next-blog-app
√ Would you like to use TypeScript? … No / Yes
√ Which linter would you like to use? » ESLint
√ Would you like to use Tailwind CSS? … No / Yes
√ Would you like your code inside asrc/directory? … No / Yes
√ Would you like to use App Router? (recommended) … No / Yes
√ Would you like to use Turbopack? (recommended) … No / Yes
√ Would you like to customize the import alias (@/*by default)? » No / Yes
√ What import alias would you like configured? … @/*
最後の「What import alias would you like configured?」では、そのまま
Enter を押下すれば、上記のように @/*
が設定されます。全ての質問に答えると各種パッケージのダウンロードとインストールを含めた環境構築が開始されます
(しばらく時間がかかります)。
最終的に以下のように Success! Created next-blog-app
というログが表示されれば成功です。
Success! Created next-blog-app at C:\Users\xxxx\Documents\next-blog-app
A new version of `create-next-app` is available!
You can update by running: npm i -g create-next-app
なお、設定を間違えてしまったときは、エクスプローラから当該フォルダ
(next-blog-app) を削除してから、再度 npx create-next-app@15
を実行してください。
3.0.1 定着確認
- 最新版の Next.js をフレームワークに使った開発環境 (の雛形)
を構築するためのコマンドを答えよ。
- 答え: npx create-next-app@latest
- 2025年11月10日現在の Next.js の最新バージョン (メジャーバージョン) を答えよ。
- 答え: 16
3.1 VSCodeの起動
作成されたプロジェクトフォルダ (next-blog-app) に移動して VSCode を起動してください。
cd next-blog-app
code .
プロジェクトは、以下のようなフォルダ構成になっているはずです。
プロジェクトフォルダを構成しているファイルについて簡単に確認していきます。
3.2 .gitignore
Gitによるバージョン管理をしないファイルやフォルダ
(=GitHubにプッシュしないファイルやフォルダ)
を設定するファイルです。ライブラリをインストールする /node_modules
フォルダなどが除外設定として記入されています。
このプロジェクトでは、.env という環境変数の設定ファイル (=
あとで「microCMSのAPIキー」や「supabseのDB接続文字列」を記述する予定のファイル)
もバージョン管理から除外したいので、次の記載があることを確認しておいてください。
.env*により .env や .env.local などのファイルがGit管理から除外されます。
3.3 eslint.config.mjs
eslint.config.mjs は、ESLint (前回講義で解説済み)
のための設定ファイルとなります。
中級者向け: ESLint設定の書式
Vite
で構築したReact環境では eslint.config.js
というファイルに、純フラット構成 で ESLint
の設定を書いていました。それに対して、Next.js 環境では 互換フラット構成
が使われており、同じ ESLint設定 でも内部構造が異なります。さらに、設定ファイルの拡張子は .js
から .mjs に変更されています。
Next.js で ESLint の設定を編集・拡張する場合には、互換フラット構成 (FlatCompat ベース) である点を踏まえて調べたり、設定変更をしてください。
(プロンプト例)
ESLint の設定ファイルの「フラット構成 (Flat Config)」と「互換フラット構成 (FlatCompat)」の違いについて説明してください。
3.4 next-env.d.ts
TypeScriptが Next.js の「組み込み機能」や「型」を認識するためのグローバル型宣言ファイルです。ファイルに書かれているように This file should not be edited (=このファイルは編集してはいけません)。
3.5 next.config.ts
next.config.ts では 外部ドメインからの画像読み込みの制御
に関する設定などを記述します。必要に応じて設定を追加したり、書き換えたりする必要があります。
3.6 package-lock.json と package.json
これらのファイルについては、既に解説済みです。"dependencies"
を確認すると、内部で React 19
を使用していることが確認できます。
package.json の "scripts" に記載の各コマンド
(dev、build、start、lint)
は次のように動作します。
"scripts": {
"dev": "next dev --turbopack",
"build": "next build --turbopack",
"start": "next start",
"lint": "eslint"
},3.6.1 npm run dev
「ホットリロード」や「エラー表示」の機能を提供する
開発サーバ
でアプリを起動します。デフォルトでは3000番ポートで起動します。実行時に プロジェクトに「.next」というフォルダ
が生成され、開発用のビルドファイルが出力されます。
- このフォルダは
.gitignoreのなかでバージョン管理の対象外に設定されています。
VSCodeのターミナル (Ctrl+Jで起動)
から実際に以下のコマンドを実行してみてください。
npm run dev
起動したサーバは http://localhost:3000/ でアクセス可能で、ターミナルから
Ctrl+C を入力すると停止可能です。停止しておいてください。
トップページ (http://localhost:3000/) の画面は、Next.js
のバージョンによっては違ったものになる可能性があります。画面上の説明も簡単に把握しておいてください。
- Get started by editing src/app/page.tsx. (まずは src/app/page.tsx を編集することから始めましょう)
- Save and see your changes instantly. (編集して保存すると、変更が直ちに画面に反映されます)
アプリの挙動がおかしいときは…
レアケースですが、npm run dev で立ち上げたアプリの挙動がおかしいときは
.next
フォルダを一旦削除することで解決することがあります。今後の開発で行き詰まったときには試してみてください。
3.6.2 npm run build
本番用 (プロダクション環境用)
にアプリをビルドします。コードの「最適化」「バンドル」「ミニファイ化」などが行われたファイルが
.next フォルダに出力されます
(ビルドをするだけでサーバは起動しません)。デプロイをする前には、必ず実行する必要があります。
実際に以下のコマンドを実行してみてください。
npm run build
ビルド処理の詳細については生成AIを使って概要を把握してください。
(プロンプト例)
Node.js + TypeScript環境のウェブアプリ開発において、ビルドプロセスで実行される「最適化」「バンドル」「ミニファイ化」とは、どのような処理ですか。
3.6.3 npm run start
ビルドされた本番用のアプリを起動します。開発サーバと同様にデフォルトでは3000番ポートで起動します。npm start
でも実行可能です。
実際に以下のコマンドを実行してみてください。
npm run start
動作が確認できたら、ターミナルから Ctrl+C
を入力してサーバを停止しておいてください。
3.6.4 npm run lint
ESLint による静的コード解析 (コード品質チェック) を実行します。このコマンドの実行結果が
✔ No ESLint warnings or errors
であれば、特に問題が検出されなかったということになります。
実際に以下のコマンドを実行してみてください。
npm run lint
なお、第03回講義で案内したように VSCode に ESLint の拡張機能をインストールしている場合は、リアルタイムにエディタ上に「警告」や「エラー」が表示されるので、基本的には、このコマンドを使用することはありません。
以下、ESLint の拡張機能がインストールされ、有効化されている前提の解説になります。
3.6.5 定着確認
- 標準設定で Next.js 15 をセットアップした場合、内部的に利用される React
のメジャーバージョンを答えよ。
- 答え : 19
3.7 postcss.config.mjs
Tailwind CSS に関する設定ファイルです。Tailwind CSS に関連するライブラリ を追加したときは、設定の追記が必要となることがあります。
3.8 README.md
就職・インターンシップ向けのポートフォリオ用として開発・公開するときは、README.md の内容を適切に記述してください。
(プロンプト例)
就職・インターンシップ向けのポートフォリオ用としてウェブアプリを開発しました。このリポジトリを公開するにあたって、README.md は、どのような構成にして、どのような内容を記載すると良いでしょうか。採用担当者の視点から解説してください。
逆にマイナスの評価や印象となるような README.md とは、どのようなものでしょうか。具体例を示しながら解説してください。
3.9 tsconfig.json
TypeScript に関する設定ファイルです。必要に応じて編集することがあります。
3.10 src/app フォルダ
Next.js では、基本的に src/app
のなかにファイルやフォルダを配置することでコンテンツを構成していきます。この際、ファイルの「名前」や「位置
(階層構造)」が、ファイルに記述されるコードの役割や機能を直接的に決定づける点
に特徴があります。この設計思想は 設定より規約(Convention over Configuration:
CoC)と呼ばれるアプローチに基づくものになっています。
Next.js における「設定より規約」の最も特徴的なものとして URLルーティング が挙げられます。
ウェブアプリにおけるルーティングとは、URLを構成するパス (例えば
/posts/1 や /about) に応じて 「どのコンテンツを表示するか」を決める仕組み を指します。
3.10.1 Next.js のルーティング
Next.js ( バージョン13から導入された AppRouter ) では、src/app
のなかに規約に従った名前でファイルを配置 (基本的には layout.tsx
もしくは page.tsx)、フォルダ階層を構成することで (自動的に)
ルーティングが設定され、フォルダ構成がそのままURLのパスとなります。
例えば、次のようにプロジェクトフォルダを構成すると…
📂 src/app/
├─ page.tsx # / のコンテンツを定義
├─ layout.tsx # 全てのページで共有されるレイアウトを定義
└─ 📂 foo/
└─ page.tsx # /foo のコンテンツを定義
次のようなルーティングルールが自動的に適用されます。
- http://xxxx:3000/ をリクエストしたときは、
src/app/layout.tsxにラップされたsrc/app/page.tsxのコンテンツが表示されます。 - http://xxxx:3000/foo
をリクエストしたときは、
src/app/layout.tsxにラップされたsrc/app/foo/page.tsxのコンテンツが表示されます。
また、layout.tsx は 入れ子構造
で適用され、親から子に順番にレイアウトが重ねられていきます。例えば、次のようにプロジェクトフォルダを構成して
http://xxxx:3000/bar/baz をリクエストすると、、、
📂 src/app/
├─ page.tsx
├─ layout.tsx # 全てのページで共有されるレイアウトを定義
└─ 📂 bar/
├─ page.tsx
├─ layout.tsx # /bar 階層下のページで共有されるレイアウトを定義
└─ 📂 baz/
└─ page.tsx # /bar/baz のコンテンツを定義
次のように複数の layout.tsx が順番に適用されます。
src/app/layout.tsxのなかに、src/app/bar/layout.tsxが配置され、- そのなか
src/app/bar/baz/page.tsxのコンテンツが表示されます。
以上のように複数の layout.tsx が上位から順にネストされ、最終的に
page.tsx のコンテンツが描画されます。
Flask のURLルーティング
前期の 知能情報実験実習1 で実装したように Python の Flask
では、@app.route() デコレータを使って
URLパスごとにコンテンツを生成してレスポンスする関数 (処理) を割りあてました。/bar と /bar/baz
は別々の関数 (@app.route('/bar') と @app.route('/bar/baz'))
で処理され、親子関係のような構造は自動的には構築されませんでした。つまり、URL構造と、プロジェクトフォルダの構造は必ずしも対応していませんでした。
App Router と Pages Router
Next.js のバージョン「12」以前では「Pages Router」というルーティングシステムが採用されていました。現在の Next.js(最新バージョンは「15」)では、2022年10月のバージョン13.0で導入され、2023年5月の13.4で安定版となった「App Router」という 新しいルーティングシステムの利用が推奨 されています。
ただし、ウェブ上の記事や生成AIの回答では、まだ Pages Router を前提とした情報が多く見られます。ウェブ検索や生成AIを利用する場合は、この点に留意してください。特に、生成AIでは 「Next.js (v14 + TypeScript + App Router) に関する質問です。」 のような文をプロンプトに含めるようにしてください。
3.10.2 定着確認
- アプリケーションの振る舞いを細かく設定ファイルで指定するのではなく、フレームワークが想定する命名規則や配置ルールに従うことで、設定コードを書く手間を省くプログラミングパラダイムのことを何というか。「XXX
よりYYY」もしくは「XXX over YYY」の形式で答えよ。
- 答え: 設定より規約。Convention over Configuration。
- Next.js 15
で使用可能な2つのルーティングシステムを答えよ。また、現在、使用が推奨されているルーティングシステムはどちらか。
- 答え: 「Pages Router」と「App Router」。現在推奨されているのは「App Router」
- Next.js で TypeScript および App Router
を採用したプロジェクトにおいて、
/aboutというURLパスにアクセスしたときに表示される画面 (UI) を作成したい。このためにsrc/app/aboutフォルダに作成すべきファイル名を拡張子を含めて答えよ (大文字と小文字を明確に区別すること)。- 答え:
page.tsx
- 答え:
4 最小構成の Next.js アプリ
まずは、最低限必要なファイルだけで、最もシンプルにアプリを構成していきます。プロジェクトフォルダの
src/app に対して、以下の作業を行なってください。
4.1 global.css の編集
global.css を次のように編集してください (先頭の1行を残して以降の内容を全て削除)。ファイルの編集後は忘れずに保存操作をしてください、以下同様。
4.2 page.tsx の編集
page.tsx を次のように書き換えてください。
"use client";
const Page: React.FC = () => {
return (
<main>
<div className="font-bold text-2xl">Main</div>
</main>
);
};
export default Page;つづいて npm run dev
を実行して結果を確認してください。以下のような表示になると思います。
Next.js において、第01行目 の "use client";
は重要な意味を持ちます。この講義では、インタラクティヴなウェブアプリを作成するため、原則として全てのTSXファイル(
ただし src/app/layout.tsx
を除く)の先頭には、この宣言を入れてください。これにより、その関数コンポーネントで
Reactの状態管理( useState や useEffect などの
ReactHooks)が使用できるようになります。
第03行目 の Page は、アロー関数形式で記述した
Reactの関数コンポーネント です。JSX (JavaScript XML)
が戻り値になっていることを確認してください。なお、型 React.FC の FCは Function Component (関数コンポーネント) の略になります。
関数コンポーネントの名前は自由に設定できますが、先頭を大文字にしてパスカルケースで命名する必要があります
(例えば page
のように先頭を小文字にすると予期せぬトラブルの原因となります)。また、page.tsx
に記述する関数コンポーネントには、第11行目のように必ず
export default キーワードを付けてデフォルトエクスポートに設定してください。
なお、型推論が働くので、第03行目の React.FC
は省略も可能であり、さらに function形式 にすれば export default
を定義部に直接的に記述可能なので、次のように書くこともできます。ただし、どちらの形式で書かれていても読解できるようになっておいてください。
"use client";
export default function Page() {
return (
<main>
<div className="font-bold text-2xl">Main</div>
</main>
);
}次項で説明しますが、同じ階層 (フォルダ内) に layout.tsx と
page.tsx が存在するときは、layout.tsx のなかの
{children} に page.tsx が埋め込まれてレンダリングされます。
4.2.1 定着確認
「リスト1」は、Next.js のプロジェクトに配置したファイルである。以下の問いに答えよ。
- 「リスト1」に示すプログラムにおいて、関数
Pageのなかで useState や useEffect などの ReactHooks を使用したい。このとき、第01行目 に記述すべき文を答えよ。- 答え:
"use client";
- 答え:
- 「リスト1」に示すプログラムにおいて、関数
Pageをデフォルトエクスポートしたい。このとき、第09行目 に記述すべき文を答えよ。- 答え:
export default Page;
- 答え:
const Page: React.FC = () => {
return (
<main>
<div className="font-bold text-2xl">Main</div>
</main>
);
};
4.3 layout.tsx の編集
Next.js において src/app/layout.tsx は アプリのレイアウトに関するエントリーポイント
(最初に読み込まれてDOMツリーを構築する基点となるファイル)
という位置づけになります。そして、このファイルには、アプリ全体のHTML構造を定義した関数コンポーネントを記述します。
この layout.tsx は、先ほどの page.tsx と同様に任意の名前の
関数コンポーネント Page: React.FC = () =>
{を記述可能ですが、次の条件を満たす必要があります。
export defaultを付与してデフォルトエクスポートに設定すること (第27行目)。- Prosp として
childrenプロパティが受け取れること (第13行目)。 "use client";を記述しないこと。- ただし
src/app直下以外に配置するlayout.tsxには"use client";を記述してください。
- ただし
以上を満たすように layout.tsx を次のように書き換えてください。
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "NextBlogApp",
description: "Built to learn Next.js and modern web development.",
};
type Props = {
children: React.ReactNode;
};
const RootLayout: React.FC<Props> = (props) => {
const { children } = props;
return (
<html lang="ja">
<body>
<header>
<div className="bg-slate-800 text-white font-bold py-2">Header</div>
</header>
<div>{children}</div>
</body>
</html>
);
};
export default RootLayout;Prosp として受け取る children には URLのパスに応じて取得されるReact関数コンポーネント
が代入されます。例えば、http://xxxx/foo
をリクエストしたときには、プロジェクトフォルダの src/app/foo/page.tsx
のなかでデフォルトエクスポートされる関数コンポーネントが children
に読み込まれます。
第04行目 から 第07行目
で設定した内容は、最終的にレンダリングされるHTMLの <head>...</head>
に反映されます。
4.4 動作確認
src/app は、次のようなファイル構成となるはずです。
npm run dev で開発モードでアプリを起動して http://localhost:3000/
にアクセスしてください。次のような画面が表示されるはずです。
4.5 Tailwind CSS 関連のLint設定
Tailwind のユーティリティクラスの自動並べ替えを行なうための設定を行ないます。基本的にはReact開発環境で設定した内容と同じです。
VSCodeの拡張機能としての Prettier (識別子:esbenp.prettier-vscode) とESLint
(識別子:dbaeumer.vscode-eslint)
はあらかじめインストールされているものとします。
VSCode でターミナルを起動し、次のように「prettier」と「eslint-plugin-tailwindcss」を開発用のパッケージとしてインストールします。
npm i -D prettier prettier-plugin-tailwindcss
つづいて eslint.config.mjs
を次のように変更してください。第15行目 から 第22行目
の設定を追加しました。
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
settings: {
tailwindcss: {
callees: ["cn", "twMerge", "tv"],
},
},
rules: {
"@typescript-eslint/no-unused-vars": "off",
},
ignores: [
"node_modules/**",
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
],
},
];
export default eslintConfig;さらに、プロジェクトフォルダの「直下」に .vscode
フォルダを作成して、そのなかに settings.json
を作成して次の内容を記述してください。
{
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnType": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"css.lint.unknownAtRules": "ignore"
}設定ファイル名は setting.json ではなく settings.json
なので注意してください。
さらに、ルートフォルダの直下に .prettierrc
を新規作成して、以下の内容を貼り付けて保存してください。
以上で設定は完了します。src/app/page.tsx を開き、その状態で保存操作をすると
Tailwind 関連のCSSクラス (ユーティリティクラス) の自動並び替え
(font-bold text-2xl 👉 text-2xl font-bold) が実行されます。
src/app/layout.tsx
についても、ファイルを開いて再保存すると並べ替えが実行されます。
5 ヘッダのコンポーネント化
Next.jsでは 再利用性 や 保守性 を高めるために、コードの「コンポーネント化」が推奨されています。コンポーネント化とは UIやロジックの共通部分を再利用可能な小さなパーツとして切り出すこと を意味します。
ここでは例として、src/app/layout.ts の 第18行目 から
第20行目 に記述している ヘッダ部
を、「Header」というコンポーネントに切り出して別ファイルで管理
する手順を説明します。
const RootLayout: React.FC<Props> = (props) => {
const { children } = props;
return (
<html lang="ja">
<body>
<header> {/* ここから */}
<div className="bg-slate-800 py-2 font-bold text-white">Header</div>
</header> {/* ここまでをHeaderコンポーネント化する */}
<div>{children}</div>
</body>
</html>
);
};まずは src/app に _components というフォルダを作成して、そこに
Header.tsx を新規作成してください。
ここでは、フォルダ名の先頭に必ず アンダーバー _
をつけるようにしてください。もし、components とすると、それは
http://localhost:3000/components
のようなルーティングの対象として機能してしまいます。一方で、先頭にアンダーバーをつけるとルーティングの対象外となり、純粋にコンポーネント用のフォルダとして扱われるようになります。
つづいて Header.tsx に以下のように Header
という関数コンポーネントを記述して保存してください。なお、慣例的に Reactの関数コンポーネントを記述するファイル名は、先頭を大文字にしたパスカルケース
(例えば Header.tsx や Button.tsx など)
で名前を付けます。ただし、Next.js の特別なファイルである page.tsx と
layout.tsx は例外的に先頭を小文字にします。
"use client";
const Header: React.FC = () => {
return (
<header>
<div className="bg-slate-800 py-2 font-bold text-white">Header</div>
</header>
);
};
export default Header;次に src/app/layout.tsx
を次のように書き換えてください。第03行目 と 第19行目
が書き換えられた部分になります。
import type { Metadata } from "next";
import "./globals.css";
import Header from "@/app/_components/Header";
export const metadata: Metadata = {
title: "NextBlogApp",
description: "Built to learn Next.js and modern web development.",
};
type Props = {
children: React.ReactNode;
};
const RootLayout: React.FC<Props> = (props) => {
const { children } = props;
return (
<html lang="ja">
<body>
<Header />
<div>{children}</div>
</body>
</html>
);
};
export default RootLayout;第03行目 では src/app/_components/Header.tsx で定義している
Header コンポーネントをインポートしています。パスの先頭を @ にすると
src を起点とした 絶対パス
でファイル位置の指定が可能になります。これは、Next.js
の環境構築で以下のように設定しているためです。
√ Would you like to customize the default import alias (@/*)? … No / Yes
√ What import alias would you like configured? … @/*
なお、import Header from "./_components/Header"
のように相対パスでも指定が可能です。プロジェクトフォルダが複雑になってくるほど、絶対パスで指定できることのメリットが大きくなります。
以上のようにインポートしたHeaderコンポーネントを、第19行目 において
<Header /> のような自己完結タグで配置しています。これは
<Header></Header> のように記述することもできます。
開発モードでブログアプリを実行して
(npm run devを実行して)、ヘッダ部をコンポーネント化する前と同様にアプリが表示されること確認してください。
5.1 FontAwesome の利用
Next.js でFontAwesomeの各種アイコンを使用する場合は少し注意が必要になってきます。
まずは、同様にパッケージをインストールしてください。
npm i @fortawesome/react-fontawesome @fortawesome/fontawesome-svg-core
npm i @fortawesome/free-solid-svg-icons
また、src/app/layout.tsx を以下のように編集します
(第04行目から第06行目の内容を記述してください。この部分が
Next.js 固有の注意点になります。
import type { Metadata } from "next";
import "./globals.css";
import "@fortawesome/fontawesome-svg-core/styles.css";
import { config } from "@fortawesome/fontawesome-svg-core";
config.autoAddCss = false;
import Header from "@/app/_components/Header";
export const metadata: Metadata = { ...
// 以下、略つづいて、実際にアイコンを使いたいコンポーネントでの処理になります。ここでは、Headerコンポーネントにサカナのアイコン() を追加していきたいと思います。
src/app/_components/Header.tsx
を次のように書き換えてください。第03行目 と 第09行目
が編集部分になります。
"use client";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFish } from "@fortawesome/free-solid-svg-icons";
const Header: React.FC = () => {
return (
<header>
<div className="bg-slate-800 py-2 font-bold text-white">
<FontAwesomeIcon icon={faFish} className="mr-1" />
Header
</div>
</header>
);
};
export default Header;各ファイルが保存されていることを確認して、アプリを実行して表示を確認してください。次のようにヘッダになアイコンがついているはずです。
なお、layout.tsx に追加した3行をコメントアウトすると (リロードしたときに)
表示が乱れることも確認してください。
5.1.1 演習
Header.tsxの 第09行目 のfaFishを、faSkullに変更してください。その他、FontAwesome の任意のフリーアイコン (CLASSIC) に変更してみてください。
5.2 インジゲータの表示/非表示の切り替え
現在、開発サーバで起動すると画面左下に、デバッグ支援のインジゲータが表示されるようになっています。クリックすると、そこから各種情報が確認できます。
このインジゲータを開発サーバでも「非表示」にしたいときは、next.config.ts
を以下のように設定してください (第05行目に設定を追加しています)。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
devIndicators: false,
};
export default nextConfig;5.3 レイアウトの調整
現状では、左端に余白がなくレスポンシブデザインにもなっていないので、ここでレイアウトについての大まかな調整していきます。次の図のようなレイアウトになるように設定していきます。
まずは、ReactによるTodoアプリ開発でも使用した「tailwind-merge」をインストールしてください。
npm i -D tailwind-merge
次に src/app/_components/Header.tsx
を次のように編集してください。第19行目 の「Header」の文字列は「XXX’s
Tech Notes」や「XXXのブログ」のように適当に書き換えてください。
"use client";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFish } from "@fortawesome/free-solid-svg-icons";
const Header: React.FC = () => {
return (
<header>
<div className="bg-slate-800 py-2">
<div
className={twMerge(
"mx-4 max-w-2xl md:mx-auto",
"flex items-center justify-between",
"text-lg font-bold text-white"
)}
>
<div>
<FontAwesomeIcon icon={faFish} className="mr-1" />
Header
</div>
<div>About</div>
</div>
</div>
</header>
);
};
export default Header;上記は、主に 第09行目 から 第16行目
に編集を加えています。ここでは解説と可読性・保守性の都合で twMerge
を使ってクラスを3分割して記述していますが、実際には twMerge を使わずに
className="..." として全てのクラスを列挙可能です。
まず、第12行目 は、max-w-2x によって div要素の最大幅 を 2xl (=672px)
に設定しています。また、mx-4 によって X方向 (左右) のマージン を
16px に設定しています。また、md:mx-auto によって画面幅が md (=768px)
を超えるときに左右のマージンを自動に設定して、要素を水平方向に中央揃えにするようにしています。
第13行目 は「 Header」と「About」を 左右の両端揃え かつ 上下中央揃え にするための設定になります。
第14行目 は「 Header」と「About」の フォントスタイルに関する設定 になります。
Tailwind CSS のクラスの詳細については、生成AIを利用して学んでください。
(プロンプト例)
Tailwind CSS を使用している環境で、div要素に “flex items-center justify-between” というクラスが設定されていました。これらのクラスはどのようなレイアウト設定か解説してください。
次に src/app/layout.tsx を次のように編集してください。
const RootLayout: React.FC<Props> = (props) => {
const { children } = props;
return (
<html lang="ja">
<body>
<Header />
<div className="mx-4 mt-2 max-w-2xl md:mx-auto">{children}</div>
</body>
</html>
);
};開発モードでブログアプリを実行して、次のようなレイアウトになっていることを確認してください。
また、Chrome のウィンドウ幅を変えたり、デベロッパーツールから、モバイル表示に切り替えてレイアウトのレスポンシブ対応を確認してください。
5.3.1 演習 ( 10分)
Header.tsx
を次のように変更すると、どのようにレンダリングされるか確認してください。
(変更前)
(変更後)
<div>
<FontAwesomeIcon icon={faFish} className="mr-1" />
Header
</div>
<div>検索</div> {/* 追加 */}
<div>About</div>さらに、次のようなレイアウトのヘッダとするためには、どのようにすればよいか考えて実装してみてください。
変更後は、一旦、元の状態に戻しておいてください。以降の解説は、元の状態を前提としたものになります。
6 ページの追加
ここでは、アプリに以下のような「About (自己紹介)」のページを追加し、サイト内外へのハイパーリンクを作成する方法、画像を表示 する方法などについて学びます。
6.1 アバター画像の準備と配置
プロジェクトフォルダの 直下 に public
という名前のフォルダがあることを確認してください。
Next.js において、プロジェクトフォルダの直下に作成した public
という名前のフォルダは特別な意味を持ち、静的アセット (= 画像、PDF、Zipファイル、ファビコン、静的なHTML/CSS、robots.txt など)
を配置するために使用します。
例えば、public に test.png という画像を配置しておけば http://localhost:3000/test.png
のようにリクエストすることで直接的に画像が取得できるようになります。
ここでは、public のなかに images
というサブフォルダを作成して「About」のページで表示するアバター画像
avatar.png を配置してください。画像は アスペクト比が「1:1」
のものを用意してください。あるいはこちらの画像を利用してください
(AIで生成した画像なので自由に利用可能です)。
(プロンプト例)
次のキーワードに基づき、JRPGスタイルのドット絵でアバター画像を作成してください。
【キャラクタ】
・(キーワードを列挙)
【背景】
・(キーワードを列挙)
【雰囲気】
・(キーワードを列挙)
6.1.1 定着確認
- Next.js
において静的アセットを配置するためのフォルダは、どのような名前で、プロジェクトフォルダのどの位置に作成すべきか答えよ。
- 答え: プロジェクトフォルダの直下に
publicという名前で配置
- 答え: プロジェクトフォルダの直下に
6.2 Aboutコンポーネントの作成
/about というURL (パス)
でアクセス可能なコンテンツを作成するために、src/app
フォルダのなかにabout というサブフォルダを作成して page.tsx
というファイルを新規作成してください。
page.tsx
を次のような内容で保存してください。そして、開発モードでアプリを起動して
http://localhost:3000/about
のURLで「About」のコンテンツにアクセスできることを確認してください。
"use client";
import Image from "next/image";
import { twMerge } from "tailwind-merge";
const Page: React.FC = () => {
return (
<main>
<div className="mb-5 text-2xl font-bold">About</div>
<div
className={twMerge(
"mx-auto mb-5 w-full md:w-2/3",
"flex justify-center"
)}
>
<Image
src="/images/avatar.png"
alt="Example Image"
width={350}
height={350}
priority
className="rounded-full border-4 border-slate-500 p-1.5"
/>
</div>
</main>
);
};
export default Page;まず、上記の 第08行目 では「About」という文字列を出力しています。
また 第16行目 から 第23行目 にかけて Image
というコンポーネントを使って「アバター画像」を出力しています (Imageコンポーネントは「第02行目」でインポートしています)。ここでは Image
の src に
"/images/avatar.png"というパスを与えていることに注目してください。このパスは、プロジェクトフォルダの
/public/images/avatar.png に対応しています。
Image要素 をラップしている div要素
では、レスポンシブデザインになるようにCSSを設定
しています。実際にブラウザの画面幅を変化させ アバター画像が適切に拡大縮小・レイアウトされること
を確認してください。CSSクラスの詳細 (特に w-full md:w-2/3 や
flex justify-center など)
については、生成AIを利用して解決してください。このあたりを理解しておくと、標準的なレイアウトはそこそこ組むことができます。
なお、このファイルでは 第05行目 のように Page
という名前で関数コンポーネントを作成していますが、AboutPage や About
のような任意の名前にすることもできます。ただし、パスカルケースとするように注意してください。
6.2.1 定着確認
- Next.js で画像を表示するための標準コンポーネント名を答えよ。
- 答え : Image
- Next.js でリンクを作成するための標準コンポーネント名を答えよ。
- 答え : Link
6.2.2 演習 (10分)
Image コンポーネントの width と height
を属性を次のように変更した場合、レスポンシブ対応を含めてレンダリング出力がどのように変化するか確認してください。確認の際には、デベロッパーツールのコンソールも同時に確認してください
(エラー出力の有無など)。
width={1000} height={1000}width={100} height={100}width={350} height={200}width={350} height={0}width={350}(heightを設定しない)- width も height も設定しない
6.3 ヘッダにAboutへのリンクを設定
ヘッダ右端に配置した「About」の文字列に /about に対する
ハイパーリンク
を設定していきます。つまり、ヘッダの「About」をクリックしたときに /about
に画面遷移するように設定していきます。
src/app/_components/Header.tsx を開いて、まずは、関数コンポーネントの定義前に
import Link from "next/link"; を追加し、その
Linkコンポーネント を使って以下のようにJSXを編集してください。
<div>
<Link href="/">
<FontAwesomeIcon icon={faFish} className="mr-1" />
Header
</Link>
</div>
<div>
<Link href="/about">About</Link>
</div>上記では「About」という文字列に /about
のハイパーリンクを設定すると共に、「Header」に /
へのハイパーリンクも設定しています。通常のHTMLでは <a href="...">...</a>
を使ってハイパーリンクを設定しますが、Next.js
では、遷移先がアプリ内となるハイパーリンクには next/link
からインポートする Linkコンポーネント を使用します。
両差の違いについては、生成AIを利用して解決してください。
(プロンプト例)
Next.jsにおいて、「“next/link” からインポートしたLinkコンポーネントを使ってハイパーリンクを作成した場合」と「通常のaタグを使ってハイパーリンクを作成した場合」の違いについて教えてください。また、どのように使い分ければよいですか。
開発モードでアプリを起動し、意図したようにハイパーリンクが機能することを確認してください。
6.4 Next.jsにおける画像の表示
Next.js では、通常のHTMLの <img> タグではなく、next/image
からインポートした Imageという組み込みのコンポーネント
を使って画像を表示することが「推奨」されています。
<Image
src="/images/avatar.png"
alt="Example Image"
width={350}
height={350}
priority
className="rounded-full border-4 border-slate-500 p-1.5"
/>Next.js における「画像の取り扱い」は、大きく「ローカル画像」と「リモート画像」の2種類に分類されます。
ここでの「ローカル画像」とは、プロジェクトフォルダ内に配置した画像のことで、これはさらに publicフォルダに配置した静的画像 と srcフォルダに配置した画像 に分けることができます。
publicフォルダの画像を Imageコンポーネントで表示するときは (面倒ですが) width
と height
の指定が必須となります。一方で、srcフォルダに配置した画像は
ビルド時に最適化 され、サイズ圧縮、フォーマット変換、複数解像度への対応など、パフォーマンスとレスポンシブ性を考慮した処理
が自動的に適用されます (Imageコンポーネントでは width と height
の省略が可能です)。
publicフォルダの配置した画像へのアクセス
publicフォルダに配置した「画像」などのコンテンツは、URLを使ってダイレクトにアクセスができます。例えば、/images/avatar.png
のように配置した画像は https://localhost:3000/images/avatar.png
で取得することができます。
一方で、srcフォルダに配置した画像については、上記のようにURLを使ってダイレクトにアクセスすることはできません。
また「リモート画像」とは「クラウドストレージ」や「外部のウェブサービス」など、アプリの実行時にプロジェクト外から取得する画像
を指します。リモート画像を使用する場合は、画像を提供しているサービスの「ドメイン」を
next.config.ts に登録しておく必要があります。
以下は、ダミー画像生成サービス placehold.jp や、avataaars generator で動的生成した画像を参照するための設定を追加した例です。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
devIndicators: false,
images: {
remotePatterns: [
{ protocol: "https", hostname: "placehold.jp" },
{
protocol: "https",
hostname: "avataaars.io",
},
],
},
};
export default nextConfig;Imageコンポーネントに関連して詳しいことが知りたい学生は公式ドキュメントを確認するか、生成AIを利用して解決してください。
- 参考 https://getavataaars.com/
(プロンプト例)
Next.js において、
next/imageからインポートした「Imageコンポーネント」を使用する際の注意点について教えてください。特に初心者がよくハマるポイントなどを教えてください。
Next.js において、
<img>タグではなく、next/imageからインポートした「Imageコンポーネント」を使って画像を表示するメリットを教えてください。
Next.js において、
srcフォルダに配置した画像については、ビルド時に最適化されると聞きました。具体的には、何のために、どのような処理が行われてるのか教えてください。
6.4.1 演習 (宿題: 30分)
Aboutページについて、「コンテンツ」と「デザイン」を各自で自由に設計して実装してください。
実装例(レスポンシブデザイン対応)
6.4.2 定着確認
- Next.js において
<a>タグの代わりに使用される組み込みコンポーネントをインポートするための文を答えよ。このコンポーネントは、プリフェッチやクライアントサイドルーティングなどの最適化機能を提供する。- 答え:
import Link from "next/link";
- 答え:
- Next.js において
<img>タグの代わりに使用される組み込みコンポーネントをインポートするための文を答えよ。このコンポーネントは、自動画像最適化、遅延読み込み、サイズ最適化などの機能を提供する。- 答え:
import Image from "next/image";
- 答え:
7 投稿記事の一覧
アプリのトップページ (/)
にアクセスしたとき、次のように「投稿記事の一覧」が表示されるような機能を実装していきます。記事の「タイトル」と「改行タグが反映された本文」に加えて、「投稿日」と「記事のカテゴリ (複数)」
についても表示されるように実装していきます。
7.1 実装の方針
ブログの投稿記事は、(最終的には) ウェブAPIを叩いて「JSON形式」でバックエンドから取得するように実装します。具体的には、次のような JSONリテラル (文字列化されたJSONデータ) をバックエンド経由でデータベースから取得することを想定します。DBは、リレーショナルデータベースを想定します。
[
{
"id": "24f932b8-231b-429b-b9dc-569f07ba16a7",
"createdAt": "2024-10-24T22:37:17.367Z",
"title": "投稿2",
"content": "夏は夜。<br/>月のころは...(略)",
"coverImage": {
"url": "https://w1980.blob.core.windows.net/pg3/cover-img-green.jpg",
"height": 768,
"width": 1365
},
"categories": [
{
"id": "587ac4ab-92de-450c-9423-5e091d16ecb5",
"name": "TypeScript"
}
]
},
{
"id": "36b7c693-4cce-4d73-afa3-acb54a404290",
"createdAt": "2024-10-22T11:22:34.684Z",
"title": "投稿1",
"content": "春はあけぼの。<br/>やうやう白くなりゆく...(略)",
"coverImage": {
"url": "https://w1980.blob.core.windows.net/pg3/cover-img-purple.jpg",
"height": 768,
"width": 1365
},
"categories": [
{
"id": "587ac4ab-92de-450c-9423-5e091d16ecb5",
"name": "TypeScript"
},
{
"id": "5cf22131-bac8-4bd0-be8e-757cec2bcc9a",
"name": "React"
}
]
}
]ただし、ウェブAPIからの取得の前に、まずはローカルに配置した
_mock/dummyPosts.ts から上記の取得することからはじめていきます。
ここで、以下の2点に注意してください。
"createdAt"は記事が作成され日時を「ISO8601形式」で表したものですが、末尾にZが付いている点です。この末尾の Z は、その日時が UTC(協定世界時)であること を示しています。そのため、「投稿1」の"2024-10-22T11:22:34.684Z"は、日本時間 (JST、UTC+9) でいえば2024年10月22日 20時22分34.684秒 を意味します。"content":には HTMLタグを含んだ文字列 が格納されていま1す。ブログ記事中の「改行」を画面に反映させるためには HTMLタグを有効化する必要がありますが、許可するタグを制限し適切なサニタイズ処理を実装しないと XSS(クロスサイトスクリプティング)攻撃のリスク が生じます。
7.1.1 定着確認
- ISO8601 形式の
"2025-11-10T18:45:15.000Z"というデータがある。これは、日本時間 (JST) でいえば、何月何日、何時何分何秒を意味するか答えよ。- 答え : 2025年11月11日 03時45分15秒
7.2 投稿記事のモックを生成
ソフトウェア開発において、実際のデータやシステムの振る舞いを模倣した仮のオブジェクトやデータ のことを モック (Mock) といいます。このブログアプリにおいても、最終的にはウェブAPIを使用してバックエンドから投稿記事のデータを取得しますが、ここではまずは「モック」を作成し、それを利用してデータを取得します。
モックを作成するにあたり、TypeScript環境では先に「型」を定義する必要があります。src/app
フォルダのなかに _types
フォルダを作成して、各型を定義した「Category.ts」「CoverImage.ts」「Post.ts」という3つのファイルを作成してしてください。
なお、型ファイルの名前は、慣例に従って パスカルケース
で命名してください。また、これらは (JSXを含まない) 純粋なTypeScriptファイルなので 拡張子は「.tsx」ではなく「.ts」 としてください
(React関数コンポーネントのように JSX構文を使うファイル だけを拡張子
.tsx としてください)。
import type { Category } from "./Category";
import type { CoverImage } from "./CoverImage";
export type Post = {
id: string;
title: string;
content: string;
createdAt: string;
categories: Category[];
coverImage: CoverImage;
};次に src/app フォルダのなかに _mocks フォルダを作成し、そこに
dummyPosts.ts というファイルを作成してください。そして、投稿記事の一覧を格納した
dummyPosts というオブジェクトを作成してください。
dummyPosts.tsのコードはこちらを参照してください。
最終的に、次のようなフォルダ構成になっていることを確認してください。
7.3 記事一覧の表示
アプリのトップページ (/)
にアクセスしたとき、次のように「投稿記事の一覧」が表示されるようにコンポーネントを追加・実装していきます。
まずは、src/app/_components のなかに、次のような PostSummary.tsx
を作成してください。PostSummaryコンポーネントは 投稿記事の「概要」を表示するためのコンポーネント になります。
"use client";
import type { Post } from "@/app/_types/Post";
type Props = {
post: Post;
};
const PostSummary: React.FC<Props> = (props) => {
const { post } = props;
return (
<div className="border border-slate-400 p-3">
<div className="font-bold">{post.title}</div>
<div>{post.content}</div>
</div>
);
};
export default PostSummary;この PostSummaryコンポーネント は Props (第04行目 から
第06行目) として、(単一の) 投稿記事 を受け取り、その
title と content を表示するコンポーネントとなっています。
次に、上記で作成した PostSummaryコンポーネント を呼び出すように
src/app/page.tsx (= / にアクセスしたとき
(トップページにアクセスしたとき) に読み込まれるファイル)
を以下のように編集してください。第04行目
で「PostSummaryコンポーネント」をインポートして、第41行目から第43行目
で mapメソッド
のなかで、呼び出しています。この処理の理解が怪しいときはTodoアプリの解説に戻って復習してください。
"use client";
import { useState, useEffect } from "react";
import type { Post } from "@/app/_types/Post";
import PostSummary from "@/app/_components/PostSummary";
import dummyPosts from "@/app/_mocks/dummyPosts";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
const Page: React.FC = () => {
// 投稿データを「状態」として管理 (初期値はnull)
const [posts, setPosts] = useState<Post[] | null>(null);
// コンポーネントが読み込まれたときに「1回だけ」実行する処理
useEffect(() => {
// 本来はウェブAPIを叩いてデータを取得するが、まずはモックデータを使用
// (ネットからのデータ取得をシミュレートして1秒後にデータをセットする)
const timer = setTimeout(() => {
console.log("ウェブAPIからデータを取得しました (虚言)");
setPosts(dummyPosts);
}, 1000); // 1000ミリ秒 = 1秒
// データ取得の途中でページ遷移したときにタイマーを解除する処理
return () => clearTimeout(timer);
}, []);
// 投稿データが取得できるまでは「Loading...」を表示
if (!posts) {
return (
<div className="text-gray-500">
<FontAwesomeIcon icon={faSpinner} className="mr-1 animate-spin" />
Loading...
</div>
);
}
// 投稿データが取得できたら「投稿記事の一覧」を出力
return (
<main>
<div className="mb-2 text-2xl font-bold">Main</div>
<div className="space-y-3">
{posts.map((post) => (
<PostSummary key={post.id} post={post} />
))}
</div>
</main>
);
};
export default Page;または、ここでは 第14行目 からの useEffect
が処理において極めて重要な意味を持ちます。useEffect
を使用することで、コンポーネントが読み込まれたときに 1回だけ ウェブAPIをコールして (現時点はモックから) データが取得される仕組み
をつくっています。
もし、useEffect を 使用せずに処理を実装をすると、次のように
無限ループ が発生します。
- ウェブAPIをコールしてデータを取得
- 取得したデータを
setPostsに与えて状態を更新 (state更新) - 状態が更新されたので再レンダリング (=Page関数が再実行される❗)
- ウェブAPIをコールしてデータを取得
- 取得したデータを
setPostsに与えて状態を更新 (state更新) - 状態が更新されたので再レンダリング (=Page関数が再実行される❗)
- 😱😱無限ループ😱😱
このような無限ループは、API使用制限(Rate Limit)への抵触、従量課金制のAPIを使用している場合は予期せぬ高額請求 (いわゆるAPI破産やクラウド破産)、サーバへの過剰な負荷 につながるため、十分に注意してください。
なお、前回講義の
でも紹介しましたが useEffect については「初心者向け!ReactのuseEffectが何してるのか20分で説明」の動画、ウェブ検索、生成AIを活用して理解を深めてください。
7.4 動作確認
ここまでの実装について、開発モードで実行確認する次のような出力が得られることが分かります。つまり、HTMLタグが サニタイズ (無害化) され出力されていることが確認できます。
Next.js (React) では、JSXで <div>{post.content}</div>
のように記述すると、悪意あるスクリプトの実行を防ぐために
コンテンツが自動的にサニタイズ (無害化・エスケープ処理) されます。
ただし、信頼できるコンテンツを HTML
として意図的に描画したい場合は、次のように dangerouslySetInnerHTML
属性を使用します。
実際に、src/app/_components/PostSummary.tsx
を書き換えて、post.content の <br/>
が「改行」に置き換わって表示されることを確認してください。
なお、改行や太字/斜線/下線などの 特定のタグだけを許可 し、それ以外のタグ
(例えば <script> などの危険なタグ) は削除したいときには
dompurify (Next.jsでバックエンドでも使用する可能性があるのであれば
isomorphic-dompurify)
などのライブラリを使用します。詳しい使い方は生成AIなどで調べてください。
- XSS脆弱性については、来年前期の「知能情報実験実習2」の前期テーマで扱います。
(プロンプト例)
TypeScript を採用した Next.js 環境において、
isomorphic-dompurifyを利用し、HTMLコンテンツから「改行」と「太字・斜線・下線」以外のタグを削除したいです。どのようにすればよいですか。
7.4.1 定着確認
- ウェブアプリにおいて、HTMLコンテンツに含まれる可能性のある「悪意のあるスクリプト」や「HTMLタグ」を無害化し、XSS攻撃を防ぐための処理のことを何というか答えよ。英語またはカタカナで答えること。
- 答え: 「サニタイズ (サニタイゼーション)」「sanitize (sanitization)」
- 「XSS攻撃」とは何の略か答えよ。英語またはカタカナで答えること。
- 答え: 「クロスサイト・スクリプティング攻撃」「Cross-Site Scripting攻撃」
7.4.2 演習 (宿題: 80分)
次のように「投稿日」と「カテゴリ」が表示されるように
src/app/_components/PostSummary.tsx
をアップデートしてください。「レイアウト」や「デザイン
(装飾)」は各自で自由に設計してください。必要に応じて dummyPosts.ts
にデータを追加してください。
必要なライブラリ (
dayjsやisomorphic-dompurifyなど) があれば適宜追加してください。本文は 最大で「3行」まで表示 されるようにしてください (4行以上になる場合は
...で省略する)。- ヒント: Tailwind CSS の
line-clamp-3を利用すると便利です
- ヒント: Tailwind CSS の
EX (任意):
src/app/page.tsxにボタンなどのUIを追加し、「日付の新しい順」「日付の古い順」に記事概要を並び替える処理を実装してください。EX (任意): 同様に、カテゴリによるフィルタ機能 (絞り込み機能) を実装してください。例えば
TypeScriptのカテゴリタグがついている記事だけを一覧で表示するなど。実装例はこちらを参照してください。