ルーティングの設定
ルーティングとは、ユーザーが特定のURLを訪れた際に、どのコンテンツを表示するかを決定する仕組みです。 SPAでは1つのページを動的に書き換えるため、何もしない限りページ内容が書き換わってもURLは変更されません。しかし、ブックマークやページ履歴を利用したい場合等、ページ内容に応じてURLを変更したい場面があります。ToDoアプリでも、ユーザー認証を作成したことで目的が異なるページ内容が複数できたため、URLでページ内容が切り替わるように実装します。
ページは次の4つに分類し、それぞれにパスを割り当てます。
/:トップページ/board:ToDoページ/signup:サインアップページ/login:ログインページ
URLと同様に、ページのtitle要素もそのままでは変わりません。本ハンズオンでは実装しませんが、変更が必要な場合にはuseEffect等を利用して実装する必要があります。
App Router
ルーティングを実現するために、Next.jsのルーティング機能であるApp Routerを使用します。
App Routerはファイルシステムに基づいてルーティングを管理する機能です。
各URLはディレクトリ構造に対応し、URLごとに使用するコンポーネントの制御や、異なるURLへの遷移などを簡単に実装できます。
具体的には、src/app配下にディレクトリを作成した後、そのディレクトリ配下にpage.tsxを作成します。page.tsxと命名したファイルを配置することで、そのディレクトリがルーティングの対象となります。例えば、src/app/board/page.tsxは/boardのパスに対応します。
pageファイルの作成
URLごとに使用するコンポーネントが切り替わるように実装します。 割り当てたパスを基に、それぞれのコンポーネントに対応するディレクトリとファイルを作成します。
トップページ
URLパスで/にあたるディレクトリとファイルを実装します。
URLパスの/にあたるファイルはsrc/app/page.tsxです。
作成済みのpage.tsxで、ページ外観の作成時に実装したWelcomeコンポーネントを呼び出します。
src/app/page.tsx
'use client';
import React from 'react';
import {Welcome} from '../components/welcome/Welcome';
export default function Home() {
return <Welcome />;
}
src/app/page.tsxにもともと実装していた<NavigationHeader />を、どの画面でも使用できるようにするためにlayout.tsxに配置します。
src/app/layout.tsx
...
import {NavigationHeader} from '../components/navigation-header/NavigationHeader';
...
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang='en'>
<body>
<NavigationHeader />
{children}
</body>
</html>
);
}
layout.tsxのbodyタグ内に{children}を記述しています。
layout.tsxは、Next.jsアプリケーションのルートレイアウトとして機能します。ルートレイアウトはアプリケーション全体で共有される<html>タグや<body>タグ、その他のグローバルなUIを定義するために使用されます。
そのため、layout.tsxで定義されたレイアウトは、src/app配下の全てのページ(page.tsx)に適用されます。
{children}は、レイアウトがラップしているページや子レイアウトなどを動的に挿入するための特別なプロパティです。
この実装により、<NavigationHeader />の下に各ページの内容が表示されるようになります。
src/app/page.tsxにもともと実装していたTodoBoardコンポーネントは、src/app配下にboardディレクトリを作成し、その中のpage.tsxで呼び出します。
これにより、{children}の位置に表示されるようになります。
(参考:layout.js | Next.js)
use clientはコンポーネントをクライアントサイドでレンダリングすることを指定できるReactのディレクティブです。
(参考:'use client' directive – React)
Next.jsではデフォルトで全てのコンポーネントがサーバコンポーネントになっています。
use clientを1行目に記述することで、そのコンポーネントと子コンポーネントがクライアントコンポーネントとして動作します。
クライアントコンポーネントは、コンポーネントの状態管理やエフェクト、イベントリスナーを使用してインタラクティブなUIを作成できます。
ハンズオンで実装したToDoの状態管理や入力、ボタンのクリックを実現するためには、それらがクライアントコンポーネントである必要があります。
したがって、 src/app/layout.tsxのbodyタグ内で呼び出されるコンポーネントには、1行目にuse clientを記述します。
ToDoページ
URLパスの/boardにあたるディレクトリとファイルを実装します。
src/app配下にboardディレクトリを作成します。
作成後、boardディレクトリ配下にpage.tsxを作成し、ToDo管理で実装したTodoBoardを呼び出します。
src/app/board/page.tsx
'use client';
import React from 'react';
import {TodoBoard} from '../../components/board/TodoBoard';
const TodoBoardPage: React.FC = () => {
return <TodoBoard />;
};
export default TodoBoardPage;
サインアップページ
URLパスの/signupにあたるディレクトリとファイルを実装します。
src/app配下にsignupディレクトリを作成します。
作成後、signupディレクトリ配下にpage.tsxを作成し、ページ外観の作成で実装したSignupコンポーネントを呼び出します。
src/app/signup/page.tsx
'use client';
import React from 'react';
import {Signup} from '../../components/signup/Signup';
const SignupPage: React.FC = () => {
return <Signup />;
};
export default SignupPage;
ログインページ
URLパスの/loginにあたるディレクトリとファイルを実装します。
src/app配下にloginディレクトリを作成します。
作成後、loginディレクトリ配下にpage.tsxを作成し、ページ外観の作成で実装したLoginコンポーネントを呼び出します。
src/app/login/page.tsx
'use client';
import React from 'react';
import {Login} from '../../components/login/Login';
const LoginPage: React.FC = () => {
return <Login />;
};
export default LoginPage;
ページ遷移の実装
各pageファイルの作成ができたため、ページ遷移を実装します。
トップページからサインアップページへの遷移
トップページからサインアップページへ遷移させるため、Welcomeを次のように実装します。
src/components/welcome/Welcome.tsx
import React from 'react';
import Link from 'next/link';
import styles from './Welcome.module.css';
export const Welcome: React.FC = () => {
return (
<div className={styles.content}>
<div>
<h1 className={styles.title}>Welcome</h1>
<div className={styles.buttonGroup}>
<Link href='/signup'>
<button className={styles.button}>登録する</button>
</Link>
</div>
</div>
</div>
);
};
「登録する」ボタンがクリックされたらサインアップページ(/signup)へ遷移するように、Linkコンポーネントを使用します。
Linkはクライアントサイドでのナビゲーションを提供するコンポーネントで、Next.jsでルート間を移動する際の推奨される方法です。
hrefプロパティには、遷移先のURLを指定します。
(参考:Link | Next.js)
サインアップ後のトップページへの遷移
サインアップしたらトップページへ遷移させるため、Signupを次のように実装します。
src/components/signup/Signup.tsx
import React from 'react';
import {useRouter} from 'next/navigation';
import styles from './Signup.module.css';
export const Signup: React.FC = () => {
const router = useRouter();
const signup: React.FormEventHandler<HTMLFormElement> = async event => {
event.preventDefault();
router.push('/');
};
return (
<div className={styles.content}>
<div className={styles.box}>
<div className={styles.title}>
<h1>ユーザー登録</h1>
</div>
<form className={styles.form} onSubmit={signup}>
<div className={styles.item}>
<div className={styles.label}>名前</div>
<input type='text' />
</div>
<div className={styles.item}>
<div className={styles.label}>パスワード</div>
<input type='password' />
</div>
<div className={styles.buttonGroup}>
<button type='submit' className={styles.button}>
登録する
</button>
</div>
</form>
</div>
</div>
);
};
Next.jsが提供しているuseRouterというフックを使うことで、コンポーネントの処理中にURL遷移を行うことができます。(参考:useRouter | Next.js)
ここでは、「登録する」ボタンをクリックしたらトップページ(/)へ遷移させます。最終的にはアカウントの登録処理が完了したら遷移するように実装しますが、ここではまず遷移のみ実装しておきます。
ナビゲーションメニューからの遷移
ヘッダの「ログイン」リンクをクリックするとログインページへ遷移し、同様にヘッダの「ログアウト」ボタンをクリックしたときにトップページへ遷移させるため、NavigationHeaderコンポーネントを次のように実装します。
src/components/navigation-header/NavigationHeader.tsx
'use client';
import React from 'react';
import styles from './NavigationHeader.module.css';
import Link from 'next/link';
export const NavigationHeader: React.FC = () => {
const logout = async () => {
window.location.href = '/';
};
return (
<header className={styles.header}>
<h1 className={styles.title}>ToDoアプリ</h1>
<nav>
<ul className={styles.nav}>
<li>
<Link href='/login'>ログイン</Link>
</li>
<li>テストユーザーさん</li>
<li>
<button type='button' onClick={logout}>
ログアウト
</button>
</li>
</ul>
</nav>
</header>
);
};
ここでは、ログアウト時にページを読み込み直してReactの状態を安全に破棄するよう、useRouterではなくwindow.location.hrefを使用します。
ログイン後のToDoページへの遷移
ログインしたらToDoページへ遷移させるため、Loginを次のように実装します。
src/components/login/Login.tsx
import React from 'react';
import {useRouter} from 'next/navigation';
import styles from './Login.module.css';
export const Login: React.FC = () => {
const router = useRouter();
const login: React.FormEventHandler<HTMLFormElement> = async event => {
event.preventDefault();
router.push('/board');
};
return (
<div className={styles.content}>
<div className={styles.box}>
<div className={styles.title}>
<h1>ログイン</h1>
</div>
<form className={styles.form} onSubmit={login}>
<div className={styles.item}>
<div className={styles.label}>名前</div>
<input type='text' />
</div>
<div className={styles.item}>
<div className={styles.label}>パスワード</div>
<input type='password' />
</div>
<div className={styles.buttonGroup}>
<button type='submit' className={styles.button}>
ログインする
</button>
</div>
</form>
</div>
</div>
);
};
動作確認
サインアップやログイン等の処理はまだ実装していませんが、ページ遷移が可能になりました。frontendディレクトリで次のコマンドを実行します。
$ npm run dev
フロントエンドアプリを起動して、動作を確認します。