6. チャットボット(フロントエンド 1)

App Router

App Routerとは

App Routerは、Next.js 13から導入された新しいルーティングシステムで、従来のファイルベースのルーティングより柔軟で強力な機能となりました。

App Routerの主要機能

ファイルベースのルーティング

Next.jsではファイル構造に基づいて自動的にルーティングされます。 appディレクトリ内のファイルとフォルダがそのままルートになります。

Layoutとページの分離

レイアウト「app/layout.tsx」を定義することで、各ページ間で共有レイアウトを利用できます。

Server ComponentsとClient Components

Next.jsでは、Server ComponentsClient Componentsの両方をサポートします。SSR(サーバー側でレンダリングされるコンポーネント)とCSR(クライアント側で動作するコンポーネント)を使い分けることができます。

トップページのルーティング

App Routerでは「app」ディレクトリの中に「page.*」ファイルが存在すると自動ルーティングされます。

  • 拡張子の部分は「jsx」「tsx」

レイアウトとコンポーネント

Next.jsではデフォルトでは「layout.tsx」がレイアウト、「page.tsx」がページコンポーネントになっています。

トップページ
URI レイアウト ページコンポーネント
http://localhost:3000/ app/layout.tsx app/page.tsx

フロントエンド作成

ファイル構成

my-app/
├── app/
│   ├── api/
│   │   └── chat/
│   │       └── route.ts
│   └── page.tsx
├── styles/
│   └── globals.css
├── .env.local
├── tailwind.config.js
├── tsconfig.json
└── package.json

レイアウト

レイアウト修正

レイアウトファイル「app/layout.tsx」を開きます。

children

Next.jsのレイアウトファイルは、{children} の部分に各ページコンポーネントが表示されるように設計されています。

app/layout.tsx
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className="container">{children}</body>
    </html>
  );
}

Metadata修正

「metadata」 の「title」「description」を修正します。

app/layout.tsx
// Metadata修正
export const metadata: Metadata = {
  title: "Gemini Chatbot",
  description: "Gemini APIを利用したチャットボットアプリです。",
};

CSS修正

「app/layout.tsx」のデフォルトは、同階層の「globals.css」を読み込んでいます。

app/layout.tsx
import type { Metadata } from "next";
// globals.css 読み込み
import "./globals.css";

「app/globals.css」を開きます。

余分なCSSを削除し、TailwindCSSだけにします。

@tailwind base;
@tailwind components;
@tailwind utilities;

動作確認

ページタイトルや説明文が変更されました。このように各ページの共通部分(HTMLの基本構造やナビゲーションなど)はレイアウトで管理します。

トップページ修正

今回のページはCSRに対応したClient Componentで作成します。

ページコンポーネントを開く

トップページのコンポーネント「app/page.tsx」を開きます。

use client

先頭にuse clientを追加してClient Componentにします。

app/page.tsx
// 追加:Client Componentとする 
'use client';

import { useState } from "react";

ページコンポーネント修正

JSXを修正します。

app/page.tsx
'use client';

export default function Home() {
    return (
        <main className="flex flex-col justify-center">
            <h1 className="text-2xl p-5">Gemini Chatbot</h1>
        </main>
    );
}

動作確認

トップページが変更されました。

メッセージ入力処理

フォーム作成

メッセージの入力フォームを作成します。

app/page.tsx
'use client';

export default function Home() {
  return (
    <main className="flex flex-col justify-center">
      <div className="bg-white shadow-md p-4 z-10">
        <h1 className="text-2xl p-5">Gemini Chatbot</h1>
        <div className="flex">
          <input
            type="text"
            className="w-full p-2 border border-gray-300 rounded-l mr-0"
            placeholder="Type your message..."
          />
          <button className="w-1/6 bg-blue-500 text-white p-2 rounded-r">
            Send
          </button>
        </div>
      </div>
    </main>
  );
}

フォーム確認

入力フォームが表示されるか確認します。

イベント処理

テキストボックスに入力したデータを設定するようにします。

データ定義

入力データの定義します。

app/page.tsx
export default function Home() {
    // 入力データ定義
    const [inputMessage, setInputMessage] = useState<string>('');

    ...
}

イベントハンドラー作成

changeMessageHandler() を追加します。

app/page.tsx
export default function Home() {
    const [inputMessage, setInputMessage] = useState<string>('');

    const changeMessageHandler = (e: React.ChangeEvent<HTMLInputElement>) => {

    };
    ....
}

テキストボックスの値を inputMessage() に設定します。

app/page.tsx
export default function Home() {
    const [inputMessage, setInputMessage] = useState<string>('');

    const changeMessageHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
        // デバック用
        console.log(e.target.value);
        // テキストボックスのデータを inputMessage に設定
        setInputMessage(e.target.value);
    };

    ....
}

イベント追加

inputタグにonChangeイベントを追加し、値には「inputMessage」を設定します。

app/page.tsx
export default function Home() {
    ...

    return (
        <main className="flex flex-col justify-center">
            <div className="bg-white shadow-md p-4 z-10">
                <h1 className="text-2xl p-5">Gemini Chatbot</h1>
                <div className="flex">
                    <input
                        onChange={changeMessageHandler}
                        value={inputMessage}
                        type="text"
                        className="w-full p-2 border border-gray-300 rounded-l mr-0"
                        placeholder="Type your message..."
                    />
                    <button className="w-1/6 bg-blue-500 text-white p-2 rounded-r">
                        Send
                    </button>
                </div>
            </div>
        </main>
    );
}

動作確認

テキストボックスに入力するたびに、メッセージがコンソール表示されるか確認します。

クリックイベント

ボタンをクリックしたら、メッセージをAPIに送信するようにします。

イベントハンドラー追加

ボタンクリックのイベントハンドラーを作成します。

app/page.tsx
export default function Home() {
    ...

    const sendHandler = async (e: React.MouseEvent<HTMLButtonElement>) => {
        if (inputMessage.trim() === '') return;

        setInputMessage('');
    };

    ...
}

イベント追加

buttonタグにonClickイベントを追加します。

app/page.tsx
export default function Home() {
    ...

    return (
        <main className="flex flex-col justify-center">
            <div className="bg-white shadow-md p-4 z-10">
                <h1 className="text-2xl p-5">Gemini Chatbot</h1>
                <div className="flex">
                    <input
                        onChange={changeMessageHandler}
                        value={inputMessage}
                        type="text"
                        className="w-full p-2 border border-gray-300 rounded-l mr-0"
                        placeholder="Type your message..."
                    />
                    <button className="w-1/6 bg-blue-500 text-white p-2 rounded-r"
                        onClick={sendHandler}>
                        Send
                    </button>
                </div>
            </div>
        </main>
    );
}

動作確認

【Send】ボタンをクリックして、テキストボックスがブランクになるか確認します。