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

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

Recruit PRO
August 05, 2026

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

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

Recruit PRO

August 05, 2026

Transcript

66 slides
  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

More Decks by Recruit

Other Decks in Technology