前回の記事でNext.jsの開発環境を整えました。
今回はいよいよ「ページを追加する」体験をします。
Next.jsのルーティングは独特で、最初は「え、こんなに簡単でいいの?」と拍子抜けするかもしれません。
設定ファイルを書く必要はありません。
フォルダとファイルを置くだけでURLが生まれます。
Contents
App Routerの基本ルール
Next.jsの app/ フォルダは特別な場所です。
ここに page.tsx というファイルを置くと、そのフォルダ構造がそのままURLになります。
app/
├── page.tsx → localhost:3000/
├── about/
│ └── page.tsx → localhost:3000/about
└── blog/
├── page.tsx → localhost:3000/blog
└── first-post/
└── page.tsx → localhost:3000/blog/first-postルールはたったひとつ:ページとして表示したいファイルは page.tsx という名前にする。
フォルダ名がそのままURLのパスになります。
直感的ですよね。
実際にページを追加してみる
前回作ったプロジェクト(my-first-nextjs)を開いて、開発サーバーを起動しておいてください。
npm run devAboutページを作る
app/ の中に about フォルダを作り、その中に page.tsx を作成します。
// app/about/page.tsx
export default function AboutPage() {
return (
<main>
<h1>このサイトについて</h1>
<p>Next.jsの練習で作ったサイトです。</p>
</main>
);
}ブラウザで http://localhost:3000/about を開くと、作ったページが表示されます。設定ファイルを一切触らずに、です。
Blogページも作る
同じ要領で app/blog/page.tsx を作ります。
// app/blog/page.tsx
export default function BlogPage() {
return (
<main>
<h1>ブログ</h1>
<p>記事一覧がここに並びます。</p>
</main>
);
}http://localhost:3000/blog でアクセスできます。
ページ間をリンクで繋ぐ
ページを作ったら、行き来できるようにしましょう。HTMLでいう <a> タグの代わりに、Next.jsでは <Link> コンポーネントを使います。
// app/page.tsx(トップページ)
import Link from 'next/link';
export default function Home() {
return (
<main>
<h1>トップページ</h1>
<nav>
<Link href="/about">このサイトについて</Link>
<Link href="/blog">ブログ</Link>
</nav>
</main>
);
}なぜ <a> ではなく <Link> を使うの?
通常の <a> タグでページ遷移すると、ブラウザがページ全体を再読み込みします。
<Link> を使うと、Next.jsが差分だけを更新するのでページ遷移が高速になります。
また、リンク先のページを事前に読み込んでおく(プリフェッチ)機能も自動で働きます。
基本的に、Next.jsアプリ内のリンクは常に <Link> を使うと覚えておいてください。
動的ルート — URLにIDを含める
ブログ記事一覧を作るとき、/blog/1、/blog/2、/blog/3 のように記事ごとにURLが変わります。
記事の数だけページファイルを作るのは非現実的ですよね。
そこで使うのが動的ルートです。フォルダ名を角括弧 [...] で囲むと、URLの一部を変数として受け取れます。
app/
└── blog/
├── page.tsx → /blog
└── [id]/
└── page.tsx → /blog/1、/blog/2、/blog/abc … なんでも受け取れる[id] フォルダの中の page.tsx でURLの値を受け取る方法はこうです。
// app/blog/[id]/page.tsx
type Props = {
params: Promise<{ id: string }>;
};
export default async function BlogPostPage({ params }: Props) {
const { id } = await params;
return (
<main>
<h1>記事 #{id}</h1>
<p>ここに記事の内容が入ります。</p>
</main>
);
}http://localhost:3000/blog/42 にアクセスすると「記事 #42」と表示されます。
/blog/hello-world なら「記事 #hello-world」です。
💡ポイント:
Next.js 15以降、
paramsは非同期(Promise)になりました。
await paramsで値を取り出してから使います。
動的ルートとリンクを組み合わせる
ブログ一覧ページ(/blog)から各記事へリンクを張ってみましょう。
// app/blog/page.tsx
import Link from 'next/link';
const posts = [
{ id: '1', title: 'Next.jsを始めてみた' },
{ id: '2', title: 'App Routerが便利すぎる件' },
{ id: '3', title: 'Tailwind CSSとの組み合わせ' },
];
export default function BlogPage() {
return (
<main>
<h1>ブログ</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
</main>
);
}一覧の各リンクをクリックすると、動的ルートのページに飛べます。
今はデータをファイル内に直書きしていますが、後の記事でAPIから取得する方法を紹介します。
存在しないページを開いたら?(404ページ)
/blog/999 にアクセスしたとき、記事が存在しない場合にエラーを表示したいことがあります。
Next.jsには notFound() という関数が用意されています。
// app/blog/[id]/page.tsx
import { notFound } from 'next/navigation';
const posts: Record<string, string> = {
'1': 'Next.jsを始めてみた',
'2': 'App Routerが便利すぎる件',
};
type Props = {
params: Promise<{ id: string }>;
};
export default async function BlogPostPage({ params }: Props) {
const { id } = await params;
const title = posts[id];
if (!title) {
notFound(); // 存在しないIDなら404を表示
}
return (
<main>
<h1>{title}</h1>
</main>
);
}notFound() を呼ぶと、Next.jsが自動で404ページを表示します。
カスタムの404デザインを用意したい場合は app/not-found.tsx を作るだけでOKです。
// app/not-found.tsx
import Link from 'next/link';
export default function NotFound() {
return (
<main>
<h1>404 — ページが見つかりません</h1>
<Link href="/">トップページへ戻る</Link>
</main>
);
}ここまでのフォルダ構成
今回作ったファイルをまとめると、こうなっています。
app/
├── page.tsx → /
├── not-found.tsx → 404ページ
├── about/
│ └── page.tsx → /about
└── blog/
├── page.tsx → /blog
└── [id]/
└── page.tsx → /blog/:idこれだけのファイルで5つのルートが機能しています。
設定ファイルは0行です。
まとめ
この記事で学んだこと:
app/フォルダのファイル構造がそのままURLになる(App Router)- ページとして表示するファイルは必ず
page.tsxという名前にする - サイト内のリンクは
<Link>コンポーネントを使う(<a>タグではなく) - フォルダ名を
[id]のように角括弧で囲むと、URLの値を受け取れる(動的ルート) notFound()で404ページを表示でき、not-found.tsxでデザインをカスタマイズできる
次の記事では、全ページ共通のヘッダーやフッターを作るための レイアウト と、コンポーネントの設計について掘り下げます。


























