はじめてのApp Router
理屈の重さに反して、書き味はなかなか良いフレームワークに仕上がっておりますので、ここでひとつ、簡単なアプリケーションを作りながら、App Routerの基本的な使い方を体験してみましょう。
題材として、ソーシャルニュースサイト「Hacker News」のJSON Web APIである、Hacker News APIからデータを取得して、画面に表示するWebサイトを作っていくことにしましょう。
Hacker Newsの人気・最新の記事500件を取得できる /topstories.json
というエンドポイントがあるのですが、500件は多すぎるので、このエンドポイントの上位20件を読める機能にしてみましょう(図2)。
左サイドのメニューに記事の概要を表示して、右側のコンテンツ部に記事の詳細を出す形にします。
題材とするAPIの特性
実は、このAPIは少し厄介な特性を持っています。「記事一覧」や「コメント一覧」といったデータを、リスト3のようなIDの配列として返してきます。
[ 36264744, 36265771, 36261411, 36256517, 36263349, ...]
タイトルや本文を取得したい場合は、IDを使って /item/<id>
のようにエンドポイントにアクセスすることで、該当の記事データやコメントデータを取得することになります。つまり、今回のように「上位20件の記事とそこに付随するコメントを読めるアプリ」を作る場合、次のような通信が必要になります。
1. /topstories.json
で500件分の記事IDを取得する
2. 1の先頭20件分の記事IDを利用して、/item/<id>
に20回アクセスして、左サイドメニューに表示するためのタイトルを入手する
3. 記事のページを開くまでに、/item/<id>
にアクセスして記事のタイトルや本文を取得する
4. 記事データにはコメントIDのリストが含まれているので、コメントIDの1件ずつについて /item/<id>
へのアクセスを行い、コメントデータを取得する(なおコメントの数は不定で、何回アクセスが発生するかはわからない)
場合によっては1ページを開くだけで数十〜100回以上の通信が必要になることがお分かりいただけるでしょうか。ブラウザ上だけで動くアプリケーションとして実装するには、あまりにも過酷な条件ですが、App Routerを通じてサーバーの力を借りれば、ブラウザがアクセスするネットワークの負担を最小限にできます。
Next.jsプロジェクトをApp Router向けにセットアップする
Next.jsのプロジェクトは create-next-app
というCLIツールを使ってセットアップします。いくつか選択肢が出てきますが、リスト4を参考にしてください。
$ npx create-next-app@latest
✔ What is your project named? … hackernews-app
✔ Would you like to use TypeScript with this project? … No
✔ Would you like to use ESLint with this project? … No
✔ Would you like to use Tailwind CSS with this project? … No
✔ Would you like to use src/
directory with this project? … No
✔ Use App Router (recommended)? … Yes
✔ Would you like to customize the default import alias? … No
今回は次の選択肢を選びました。
- アプリ名:hackernews-app
- TypeScriptを利用しますか?:いいえ
- ESLintを利用しますか?:いいえ
- Tailwind CSSを利用しますか?:いいえ
-
src/
ディレクトリを利用しますか?:いいえ - App Routerを利用しますか?:はい
- インポートのエイリアス機能をカスタマイズしますか?:いいえ
サンプルとしての簡単さのために、App Routerを利用する以外の選択肢は「いいえ」で答えましたが、慣れていればTypeScriptやESLintは是非利用してください。
しばらく待つと、セットアップが完了します。せっかくなので、一度起動してみましょう(リスト5)。
$ cd hackernews-app $ npm run dev > hackernews-app@0.1.0 dev > next dev - ready started server on 0.0.0.0:3000, url: http://localhost:3000
サーバーが起動したので、メッセージに表示された http://localhost:3000
にアクセスしてみましょう(図3)。
チュートリアルや公式ドキュメントへのリンクが並んでいます。ひとまず、これで起動できることが確認できました。
pageとlayout
ページをカスタマイズする前に、現状のappフォルダがどうなっているのかを確認しておきましょう(図4)。
重要なファイルは2つだけで、ページを表す page.js
と、それを囲むレイアウトを表す layout.js
です。
page.js
には、そのフォルダ階層で表示すべきページが定義されています。app
フォルダ内のフォルダ階層は、URL上のパスを表しており、その中に page.js
という名前でファイルが定義されていると、そこからデフォルトエクスポートされたコンポーネントを画面に表示します。app/page.js
は app
フォルダ内でいうとルートフォルダにあたるので、URLとしてはルート /
のコンテンツを表すことになります。page.js
というファイル名のファイルが存在しないフォルダ階層は、有効なルートとして認識されず、404ページが表示されます。
同じように、ファイル名が重要なのが layout.js
です。layout.js
は各フォルダ階層で必須ではありませんが、配置することで、そのフォルダ階層以下の全てのページの外側に被さるコンポーネントを定義することができます。app/layout.js
に定義したコンポーネントは、全てのパスの外側に被せられることになります。この説明だけだと分かりづらいかもしれないので、app/layout.js
を見てみましょう(リスト6)。
import './globals.css' import { Inter } from 'next/font/google' const inter = Inter({ subsets: ['latin'] }) export const metadata = { title: 'Create Next App', description: 'Generated by create next app', } export default function RootLayout({ children }) { // (1) return ( <html lang="en"> <body className={inter.className}>{children}</body> </html> ) }
Webページにおいて、ルートパス( /
)以下の全ての階層において、必ず表示されていてほしい、最も外側のコンポーネントといえば、そう、<html>
や <body>
ですね。App Routerでは、<html>
や <body>
を定義するための特別なコンポーネントを用意するのではなく、任意の階層の外側に被せるための layout.js
という仕組みを使って、HTMLとしての外側を定義しています。
layout.js
が通常のコンポーネントと違う点は1つだけで、必ず children
をレンダリングする必要があります。「外側に被せる」ための仕組みということで、「内側」になるページ実装が children
として渡されてきます。children
の実装を忘れると、pages.js
の内容が表示されないので、忘れずに実装しましょう。
改めて、図3で表示した初期状態のページについて、layout.js
と page.js
の関係を図示すると、図5のようになります。
ルートフォルダのレイアウトには <html>
と <body>
しかないので、初期状態で目に見えている部分は、すべて page.js
で実装されていることになります。今後扱う実装例の中では、目に見えるUIを持ったレイアウトについても扱っていくので、楽しみにしていてください。