Upgrade to Pro — share decks privately, control downloads, hide ads and more …

モダンフロントエンド 開発研修

Avatar for Recruit Recruit PRO
August 28, 2025

モダンフロントエンド 開発研修

2025年度リクルート エンジニアコース新人研修の講義資料です

Avatar for Recruit

Recruit PRO

August 28, 2025
Tweet

More Decks by Recruit

Other Decks in Technology

Transcript

  1. Profile // profile.jsonc { "name": " 佐藤 昭文", "alias": ["akfm_sato",

    " あっきー"], "job": " フロントエンドエキスパート", "tags": ["Next.js", "React", "Test", " リーン開発"], "sns": { "x": "akfm_sato", "zenn.dev": "akfm", }, }
  2. 今日のテーマ: モダンフロントエンド開発 今日の目標: フロントエンドにおけるモダンな技術の知見や感覚を養うこと ハードスキル TypeScript, Lint, Formatter, Storybook Next.js

    ソフトスキル 教え教わる技術 チーム開発の推進技術 ※ 時間の関係もあり、AI ツールの話はテーマ外とします エンジニア市場における当たり前を知っておくことは、常に重要
  3. Agenda タイムテーブル 内容 10:15~10:45 はじめに 10:45~12:00 Storybook 12:00~13:00 休憩 13:00~15:00

    Next.js 15:00~17:00 課題(ブログ開発) 17:00~18:00 振り返り 作業は個々人ですが、実案件同様チームメンバー同士教え教わりながら進めましょう
  4. プログラマーの誓い 1. 害を及ぼすようなコードは書きません。 2. 自分が書くコードがもっとも良いものであることを誓います。振る舞いや構造が悪いコードをリリースしま せん。 3. リリースごとにコードのすべてが意図通りに動くことの、速く、確実で、再現可能な証明も併せて作りま す。 4.

    他の人の進みを妨げないよう、コマ目に、小さいリリースを行います。 5. 常にコードを躊躇なく、常に改善していきます。間違ってもコードを悪くしません。 6. 自分や他の人の生産性を最も高く保つようにします。生産性を下げるようなことはしません。 7. 他の人が自分をカバーできるよう、また、自分が他の人をカバーできるように保ちます。 8. 規模と正確性において正直な見積りを出します。確信のない約束はしません。 9. 常に学び、自分の技術を磨き続けます。 https://qiita.com/diskshima/items/588c423977f42626910d
  5. モダンフロントエンド開発 題材 Storybook を使ったコンポーネント駆動開発 Tailwind CSS, shadcn/ui を使ったコンポーネントの構築 Next.js を使ったアプリケーション開発

    全体を通して利用する技術 TypeScript Lint Formatter 時間の都合で省略する技術 AI ツールの活用 テスト(Vitest/Playwright) Storybook とNext.js を中心にモダンフロントエンドの開発スタイルを学ぶ
  6. Storybook コンポーネント単位で見た目や使い方を確認することができる // button.stories.tsx import type { Meta, StoryObj }

    from "@storybook/react"; import { Button } from "./Button"; const meta: Meta<typeof Button> = { component: Button, }; export default meta; type Story = StoryObj<typeof Button>; export const Primary: Story = { args: { primary: true, label: "Button", }, };
  7. 課題: Storybook の起動 1. 研修リポジトリのREADME に従ってセットアップ 2. packages/ui のREADME に従ってStorybook

    を起動 研修のWorkspace をセットアップして、Storybook を起動してみましょう
  8. Tailwind CSS, shadcn/ui Tailwind CSS Utility 1st なCSS フレームワーク 現在のCSS

    情勢はTailwind 人気が非常に高い shadcn/ui コンポーネントライブラリ 一強まで言わずとも非常に人気 @repo/ui のコンポーネントは tailwind CSS と shadcn/ui を利用している
  9. 課題: コンポーネントの追加 1. Checkbox を追加し、 Checkbox のStory を作成 Story はどんなパターンがあるといいだろう🤔

    2. Card を追加し、 Card のStory を作成 見やすいStory にするには、どんな children がいいだろう🤔 3. その他好きにコンポーネントを導入+Story を作成してみましょう
  10. App Router app ディレクトリ配下に配置する React Server Components をサポートしてる デフォルトでServer Components

    Server -> Client の2 層構造 強力なCache その他諸々新機能(多くて混乱するので割愛) Next.js の新しいアプリケーション構築の仕組み
  11. React Server Components React Server Components(RSC) はアーキテクチャ名 Server Component: サーバー側でのみレンダリングされるComponent

    Client Component: 従来からあるクライアント・サーバーどちらでもレンダリング可能なComponent Next.js ではなくReact における新たな概念 // Server/Client Components Tree <ServerA> <ClientB> <ServerC /> </ClientB> <ClientD /> </ServerA>
  12. RSC とSSR との違い SSR: Client Components をサーバーサイドでレンダリングすること RSC: React の新たなアーキテクチャ名称

    Client Components: 従来からあるComponent Server Components: サーバーサイドでのみレンダリングされるComponent 従来からあるSSR(Server Side Rendering) と混乱しやすいが、Server Components は全く異なる概念
  13. Server Components Component 自体をasync にすることが可能(= fetch を直接的に扱える) useState などの一部hooks は利用できない

    Client Components/Server Components どちらも含めることができる サーバーでのみ実行されるComponent <Counter /> {/* Counter: Client Component */} export async function Products() { // 今までできなかったが、直接await できる const res = await fetch("https://dummyjson.com/products"); const product = await res.json(); return ( <div> <pre> <code>product: {JSON.stringify(product)}</code> </pre> </div> ); }
  14. Client Components "use client" はサーバーから見た時のクライアントサイドの入り口 import されるモジュールは、全てClient モジュールとなる 従来からあるComponent import

    { useState } from "react"; const [count, setCount] = useState(0) // Client Component のみで利用可能 "use client" import { Child } from "@/components/Child"; export function Counter() { return ( <div> <p>count: >{count}</p> <button onClick={() => setCount(prev => prev + 1)}>increment</button> <Child /> </div> ) }
  15. Client Components children ( などのprops) を除き、Server Components を含むことはできない 従来からあるComponent children:

    React.ReactNode; // Server Components も可! <div>{children}</div> "use client"; import { useState } from "react"; export function Accordion({ children, }: { }) { const [isOpen, setIsOpen] = useState(false); return ( <div> <button>toggle</button> </div> ); }
  16. Server Action "use server" はクライアントから見た時のサーバーへの入り口 form の action からサーバー側の関数を実行できる <form

    action={createTodo}> // todo-create.ts "use server"; async function createTodo(formData: FormData) { const title = formData.get("title"); // ...API へPOST したりDB に保存するなど... } // page.tsx export default function Page() { return ( <input type="text" name="title" /> <button type="submit">Create</button> </form> ); }
  17. その他多くの機能 Nested Layout Error/Loading UI dynamic route revalidate parallel route

    intercepting route etc… これらの詳細はshort tutorial で手を動かしながら
  18. 課題: Nested layout layout.tsx を修正して共通のヘッダーを作成しましょう <header>all page's header</header> // app/layout.tsx

    export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="ja"> <body> {children} </body> </html> ); }
  19. 課題: New page ディレクトリを作成して新しいページを作成しましょう // app/products/layout.tsx export default function RootLayout({

    children, }: { children: React.ReactNode; }) { return ( <> {children} <footer>products page's footer</footer> </> ); }
  20. 課題: Server Components + fetch dummy データをfetch して表示しましょう const res

    = await fetch("https://dummyjson.com/products"); const product = await res.json(); // app/products/page.tsx import { Suspense } from "react"; export default function Page() { return ( <> <h1>Product Page</h1> <Suspense fallback={<div>Loading...</div>}> <Posts /> </Suspense> </> ); } async function Posts() { return ( <pre> <code>{JSON.stringify(product, null, 2)}</code> </pre>
  21. 課題: Error UI page.tsx で強制的にエラーを起こすことで確認できます page.tsx でエラーが起きた時のUI は、 error.tsx で定義しましょう

    // app/products/error.tsx "use client"; export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { return ( <div> <h2>Error!</h2> <button onClick={() => reset()}>Try again</button> <pre>{error.message}</pre> </div> ); }
  22. 課題: Client Components + useState useState を使うために "use client" を追加しましょう

    "use client"; import { useState } from "react"; const [count, setCount] = useState(0); // app/components/counter.tsx export function Counter() { return ( <div> <p>count: {count}</p> <button type="button" onClick={() => setCount(count + 1)}> increment </button> </div> ); }
  23. 課題: Client Components + useState useState を使うために "use client" を追加しましょう

    import { Counter } from "app/components/counter"; <Counter /> // app/products/page.tsx import { Suspense } from "react"; export default function Page() { return ( <> <h1>Product Page</h1> <Suspense fallback={<div>Loading...</div>}> <Posts /> </Suspense> </> ); } // ...
  24. 課題: Server Actions form からサーバー側の関数を実行しましょう // app/products/create-post.ts "use server"; export

    async function createPost(data: FormData) { // skip validation const title = data.get("title"); console.log(`action called with title: "${title}"`); // ...API へPOST したりDB に保存するなど... }
  25. 課題: Server Actions form からサーバー側の関数を実行しましょう import { createPost } from

    "./create-post"; <form action={createPost}> title: <input type="text" name="title" /> <button type="submit">create post</button> </form> // app/products/page.tsx import { Counter } from "app/components/counter"; import { Suspense } from "react"; export default function Page() { return ( <> <h1>Product Page</h1> <Counter /> <Suspense fallback={<div>Loading...</div>}> <Posts /> </Suspense> </> ); } // ...
  26. 課題: Dynamic route 動的なURL のページを作成しましょう // app/products/page.tsx import Link from

    "next/link"; import type { Product } from "./type"; // ... async function Posts() { const res = await fetch("https://dummyjson.com/products"); const product: { products: Product[] } = await res.json(); return ( <> {product.products.map((product) => ( <div key={product.id}> <Link href={`/products/${product.id}`}>{product.title}</Link> </div> ))} </> ); }
  27. 課題: Dynamic route 動的なURL のページを作成しましょう // app/products/[id]/page.tsx import { Suspense

    } from "react"; import type { Product } from "../type"; export default async function Page({ params, }: { params: Promise<{ id: string }>; }) { const { id } = await params; return ( <Suspense fallback={<div>Loading...</div>}> <ProductDescription id={id} /> </Suspense> ); } // ... (次のページへ)
  28. 課題: Dynamic route 動的なURL のページを作成しましょう // app/products/[id]/page.tsx // ... (前のページより)

    async function ProductDescription({ id }: { id: string }) { const res = await fetch(`https://dummyjson.com/products/${id}`); const product: Product = await res.json(); return ( <div> <h1>Product {product.title}</h1> <p>{product.description}</p> </div> ); }
  29. 課題: use cache "use cache" をファイルや関数の先頭に記述することで、キャッシュを有効化できる 有効期限は cacheLife() で設定 キャッシュしたいコンポーネントや関数には

    "use cache" を追加しましょう import { unstable_cacheLife as cacheLife } from "next/cache"; cacheLife("hours"); async function Posts() { "use cache"; // ... }
  30. 課題: use cache cacheTag('tag-name') でキャッシュにタグを付与でき、Server Functions 内の revalidateTag('tag- name') でタグに紐づくキャッシュをrevalidate

    できる キャッシュしたいコンポーネントや関数には "use cache" を追加しましょう import { unstable_cacheTag as cacheTag } from "next/cache"; cacheTag("products"); async function Posts() { "use cache"; // ... }
  31. 課題: use cache cacheTag('tag-name') でキャッシュにタグを付与でき、Server Functions 内の revalidateTag('tag- name') でタグに紐づくキャッシュをrevalidate

    できる キャッシュしたいコンポーネントや関数には "use cache" を追加しましょう import { revalidateTag } from "next/cache"; revalidateTag("posts"); "use server"; export default async function revalidatePost() { // ... }
  32. 課題: ブログアプリケーション 前提 apps/blog-app ディレクトリに作りかけのブログがあります API の仕様は mock-server/openapi.json を参照してください 要件は非常に抽象的なので、周りや講師に相談しながらよしなに仕様を決めてください

    要件 1. 一覧を10 件ごとに表示するようにして下さい 2. 詳細ページを実装して下さい 3. 記事の新規作成機能を実装して下さい 4. 記事の更新ページを作ってください 5. 記事の削除機能を実装して下さい 6. form にvalidation 機能を追加して下さい 今日学んだことを活かして、ブログアプリケーションを実装しましょう
  33. 課題: 知見の共有 目的: 自分の知見を共有すること、相手から知見を得ること 5~6 人1 チームでまとまってください 1 人2~3 分程度で今日得た知見・疑問・感想を共有してください

    全員話し終えたら、気になった話題について深ぼって教えあったり議論したりしましょう 今日の研修で得た学び・知見・感想を共有しよう
  34. 今日のテーマ(再掲): モダンフロントエンド開発 今日の目標: フロントエンドにおけるモダンな技術の知見や感覚を養うこと ハードスキル TypeScript, Lint, Formatter, Storybook Next.js

    ソフトスキル 教え教わる技術 チーム開発の推進技術 エンジニア市場における当たり前を知っておくことは、常に重要
  35. 研修で触れられなかったこと 単体テスト: Vitest+react-testing-library などを AI ツール: Cursor やGitHub Copilot 、Claude

    Code など UI/UX: モダンなUX やパフォーマンスチューニング 品質保証: 体系化されたテスト、開発プロセス 今日研修で扱えなかったものの、現場では普通に使うモダンな技術
  36. End