TEXTBOOK SECTION / AI LEARNING

共通コンポーネントを作る2

Next.jsによるWebアプリケーション開発概論の「Next.jsとTailwind CSSで作る 岐阜県ローカルエンタメニュースブログアプリ教材」より、共通コンポーネントを作る2を解説。生成AI、AI活用、DX、業務改善を実践しながら学べるオンライン教材です。

4Next.jsとTailwind CSSで作る 岐阜県ローカルエンタメニュースブログアプリ教材Next.js / Tailwind CSS / アプリ開発 / 開発 / Web開発

OVERVIEW

この節で学べること

概要を表示する
項目内容
教材名Next.jsによるWebアプリケーション開発概論
Next.jsとTailwind CSSで作る 岐阜県ローカルエンタメニュースブログアプリ教材
共通コンポーネントを作る2
カテゴリNext.js / Tailwind CSS / アプリ開発 / 開発 / Web開発
学習内容生成AI、AI活用、DX、業務改善を実践しながら理解するための教材です。

TABLE OF CONTENTS

目次

CONTENT

ここから

10. 検索コンポーネントを作る

components/BlogSearch.tsx を作成します。

// components/BlogSearch.tsx

'use client';

import { BlogCard } from '@/components/BlogCard';
import { searchPosts } from '@/data/posts';
import type { BlogPost } from '@/types/blogs';
import { useMemo, useState } from 'react';

type BlogSearchProps = {
  initialPosts: BlogPost[];
};

/**
 * 役割: 記事検索フォームと検索結果一覧を表示する
 * 入力: 初期表示するブログ記事配列
 * 出力: 検索可能な記事一覧UI
 */
export function BlogSearch({ initialPosts }: BlogSearchProps) {
  const [keyword, setKeyword] = useState<string>('');

  const filteredPosts = useMemo<BlogPost[]>(() => {
    if (keyword.trim().length === 0) {
      return initialPosts;
    }

    return searchPosts(keyword);
  }, [keyword, initialPosts]);

  return (
    <section id="posts" className="mx-auto max-w-6xl px-5 py-14">
      <div className="mb-8 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
        <div>
          <p className="text-sm font-bold uppercase tracking-[0.24em] text-slate-400">
            Articles
          </p>
          <h2 className="mt-2 text-3xl font-black tracking-tight text-slate-950">
            最新ニュース
          </h2>
        </div>

        <label className="w-full md:w-96">
          <span className="mb-2 block text-sm font-bold text-slate-600">
            記事を検索
          </span>

          <input
            value={keyword}
            onChange={(event) => setKeyword(event.target.value)}
            placeholder="例: 岐阜市 / 音楽 / 大垣"
            className="w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm font-medium text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-slate-950 focus:ring-4 focus:ring-slate-200"
            type="search"
          />
        </label>
      </div>

      {filteredPosts.length > 0 ? (
        <div className="grid gap-5 md:grid-cols-2">
          {filteredPosts.map((post) => (
            <BlogCard key={post.id} post={post} />
          ))}
        </div>
      ) : (
        <div className="rounded-3xl border border-dashed border-slate-300 bg-white p-10 text-center">
          <p className="text-base font-bold text-slate-800">
            該当する記事が見つかりませんでした。
          </p>
          <p className="mt-2 text-sm text-slate-500">
            キーワードを変えて検索してください。
          </p>
        </div>
      )}
    </section>
  );
}

なぜ 'use client' が必要なのか

検索欄は、ユーザーが文字を入力します。

つまり、画面上で状態が変わります。

Reactで状態を扱うには useState を使います。

そのため、このファイルには 'use client' が必要です。

'use client';

Next.jsでは、何でもクライアント側にするのではなく、必要な部分だけクライアント側にするのが基本です。


11. 全体レイアウトを作る

app/layout.tsx を編集します。

// app/layout.tsx

import { Header } from '@/components/Header';
import type { Metadata } from 'next';
import './globals.css';

export const metadata: Metadata = {
  title: '岐阜ローカルエンタメ通信',
  description:
    '岐阜県で起きた架空のローカルエンタメニュースを紹介するブログアプリです。',
};

type RootLayoutProps = {
  children: React.ReactNode;
};

/**
 * 役割: アプリ全体のHTML構造と共通レイアウトを定義する
 * 入力: children
 * 出力: ヘッダー付きのHTMLレイアウト
 */
export default function RootLayout({ children }: RootLayoutProps) {
  return (
    <html lang="ja">
      <body>
        <Header />
        {children}
      </body>
    </html>
  );
}

layout.tsx は、全ページ共通の土台です。

ここに Header を置くことで、トップページにも記事詳細ページにもヘッダーが表示されます。


12. CSSを整える

app/globals.css を編集します。

@import "tailwindcss";

:root {
  --background: #f8fafc;
  --foreground: #0f172a;
}

html {
  scroll-behavior: smooth;
}

body {
  min-height: 100vh;
  background: var(--background);
  color: var(--foreground);
  font-family:
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    sans-serif;
}

::selection {
  background: #0f172a;
  color: #ffffff;
}

Tailwind CSS v4では、基本的に以下を書きます。

@import "tailwindcss";

これだけでTailwindのクラスが使えるようになります。

FAQ

よくある質問

共通コンポーネントを作る2は医療関係者向けだけの内容ですか。
医療分野の例が含まれる場合もありますが、医療関係者だけに限定した内容ではありません。生成AI、AI活用、DX、業務改善、プロトタイプ開発など、一般的なAI学習の事例として読める内容です。
AI初心者でも読めますか。
はい。AIをこれから学ぶ方、数学が苦手な方、仕事でAIを使いたい方にも読み進めやすいように、教材の章と節の流れに沿って整理しています。
サムネイル画像は必ず表示されますか。
はい。教材にcoverUrlが設定されている場合はその画像を表示し、未設定の場合は代替サムネイル画像を表示します。
Next.jsによるWebアプリケーション開発概論のほかの章も読めますか。
はい。教材トップから章立てを確認でき、前後の節へもページ下部のナビゲーションから移動できます。