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

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

Avatar for Recruit Recruit PRO
August 05, 2026

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

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

Avatar for Recruit

Recruit PRO

August 05, 2026

More Decks by Recruit

Other Decks in Technology

Transcript

  1. Profile // profile.jsonc { "name": " ", "alias": ["akfm_sato", "

    "specialty": " ", "assignment": " ", "tags": ["Next.js", "UX"], "sns": { "X": "akfm_sato", "Zenn": "akfm", }, } 佐藤 昭文 あっきー"], フロントエンド 住まい
  2. プログラマーの誓い https://qiita.com/diskshima/items/588c423977f42626910d 害を及ぼすようなコードは書きません。 2. 自分が書くコードがもっとも良いものであることを誓います。振る舞いや構造が悪いコードをリリースしま せん。 3. リリースごとにコードのすべてが意図通りに動くことの、速く、確実で、再現可能な証明も併せて作りま す。 4.

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

    Next.jsを使ったアプリケーション開発 全体を通して利用する技術 Storybook TypeScript Lint Formatter 時間の都合で省略する技術 AIツールの活用 テスト(Vitest/Playwright)
  4. コンポーネント指向とは コンポーネント: の構成部品 UI 公式ドキュメント - 隅から隅までコンポーネント ほとんどの React アプリでは隅から隅までコンポーネントが使われます。つまり、ボタンのような再利用可能なところでのみ使うので

    はなく、サイドバーやリスト、最終的にはページ本体といった大きなパーツのためにも使うのです。コンポーネントは、1 回しか使わな いような UI コードやマークアップであっても、それらを整理するための有用な手段です。
  5. 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", }, };
  6. Tailwind CSS, shadcn/ui @repo/ui のコンポーネントは tailwind CSS と shadcn/ui を利用している

    Tailwind CSS な フレームワーク 現在のCSS情勢はTailwind人気が非常に高い Utility 1st CSS shadcn/ui コンポーネントライブラリ 一強まで言わずとも非常に人気
  7. 課題: コンポーネントの追加 を追加し、 Checkbox のStoryを作成 Storyはどんなパターンがあるといいだろう🤔 2. Cardを追加し、 Card のStoryを作成

    見やすいStoryにするには、どんな children がいいだろう🤔 3. その他好きにコンポーネントを導入+Storyを作成してみましょう 1. Checkbox
  8. React Server Components React Server Components(RSC)はアーキテクチャ名 Server Component: サーバー側でのみレンダリングされるComponent Client

    Component: 従来からあるクライアント・サーバーどちらでもレンダリング可能なComponent データとUIをまとめてComponentとして考える設計思想に基づく
  9. と との違い RSC SSR 従来からあるSSR(Server Side Rendering)と混乱しやすいが、Server Componentsは全く異なる概念 SSR: Client

    Componentsをサーバーサイドでレンダリングすること RSC: Reactの新たなアーキテクチャ名称 Client Components: 従来からあるComponent Server Components: サーバーサイドでのみレンダリングされるComponent
  10. Server Components サーバーでのみ実行されるComponent Component自体をasyncにすることが可能(= fetch を直接的に扱える) useState などの一部hooksは利用できない Client Components/Server

    Componentsどちらも含めることができる 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> <Counter /> {/* Counter: Client Component */} </div> );
  11. Client Components 従来からあるComponent "use client" はサーバーから見た時のクライアントサイドの入り口 import されるモジュールは、全てClientモジュールとなる "use client"

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

    useState } from "react"; export function Accordion({ children, }: { children: React.ReactNode; // Server Components }) { const [isOpen, setIsOpen] = useState(false); } return ( <div> <button>toggle</button> <div>{children}</div> </div> ); も可!
  13. Server Action formの action からサーバー側の関数を実行できる "use server" はクライアントから見た時のサーバーへの入り口 // 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 ( <form action={createTodo}> <input type="text" name="title" /> <button type="submit">Create</button> </form> ); }
  14. 課題: Helloにアクセスして、 world localhost:3000 のルートディレクトリより # Git $ cd apps/learn-next

    $ pnpm dev のページが表示されることを確認しましょう Hello world
  15. 課題: Nested layout を修正して共通のヘッダーを作成しましょう layout.tsx // app/layout.tsx export default function

    RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="ja"> <body> <header>all page's header</header> {children} </body> </html> ); }
  16. 課題 : New page ディレクトリを作成して新しいページを作成しましょう // app/page.tsx import Link from

    "next/link"; export default function Page() { return ( <> <h1>Hello, Next.js!</h1> <Link href="/products">/products</Link> </> ); }
  17. 課題 : New page ディレクトリを作成して新しいページを作成しましょう // app/products/layout.tsx export default function

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

    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() { const res = await fetch("https://dummyjson.com/products"); const product = await res.json(); return ( <pre> <code>{JSON.stringify(product, null, 2)}</code> </pre>
  19. 課題: でエラーが起きた時の 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> ); }
  20. 課題: を使うために Client Components + useState を追加しましょう useState "use client"

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

    // 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> </> ); } // ...
  22. 課題からサーバー側の関数を実行しましょう : 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 ... } へ したり に保存するなど
  23. 課題からサーバー側の関数を実行しましょう : Server Actions form // app/products/page.tsx import { Counter

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

    type Product = { id: number; title: string; description: string; };
  25. 課題 : 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> ))} </> ); }
  26. 課題 : 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> ); // ... (次のページへ)
  27. 課題 : 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> );
  28. 課題 : use cache キャッシュしたいコンポーネントや関数には を追加しましょう cacheTag('tag-name') でキャッシュにタグを付与でき、Server Functions内の updateTag('tagname')

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

    でタグに紐づくキャッシュをrevalidateできる "use server"; import { updateTag } from "next/cache"; export default async function revalidatePost() { // ... } updateTag("posts"); "use cache"
  30. 課題 : ブログアプリケーション 今日学んだことを活かして、ブログアプリケーションを実装しましょう 前提 ディレクトリに作りかけのブログがあります APIの仕様は mock-server/openapi.json を参照してください 要件は非常に抽象的なので、周りや講師に相談しながらよしなに仕様を決めてください

    要件 1. 一覧を10件ごとに表示するようにして下さい 2. 詳細ページを実装して下さい 3. 記事の新規作成機能を実装して下さい 4. 記事の更新ページを作ってください 5. 記事の削除機能を実装して下さい 6. formにvalidation機能を追加して下さい apps/blog-app
  31. End