POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

FeedlyRSSXFacebook
Elvis Sautet

本記事は、原著者の許諾のもとに翻訳・掲載しております。

数週間前、私たちの本番アプリがハングし始めました。コンポーネントがランダムに読み込まれなくなったのです。ユーザーの画面ローディングスピナーの前で固まってしまいました。40時間デバッグした末に、私たちは気づきました。React Server Components(RSC)が問題だったのです。


イントロダクション:理想 vs. 現実

当初、React Server Components(RSC)は革命的であるはずでした。 Reactチームは以下を強調していました:

  • ✅ パフォーマンスの向上
  • ✅ バンドルサイズの削減
  • ✅ 自動的なコード分割
  • ✅ コンポーネントからのダイレクトなデータベースアクセス

私たちは彼らを信頼し、Next.jsアプリ全体をServer Componentsと共にApp Routerへ移行しました。

3カ月後、私たちのアプリは以下の状況に陥りました:

  • 初期ロードが遅い
  • デバッグがより複雑に
  • 経験の浅い開発者にとって理解しにくい
  • 原因不明なキャッシュ問題に悩まされている

この記事は、Reactコミュニティーが必要としている率直な会話です。マーケティングでも、誇張でもありません。React Server Componentsを使った、本番環境でのリアルな体験談です。


Part 1:React Server Componentsとは何か(シンプルバージョン)

従来モデル(Client Components)

// This runs in the browser
'use client'

export default function UserProfile() {
  const [user, setUser] = useState(null)

  useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(setUser)
  }, [])

  if (!user) return <div>Loading...</div>

  return <div>{user.name}</div>
}

処理フロー

  1. ブラウザーがJavaScriptをダウンロードする
  2. コンポーネントがマウントされる
  3. useEffectが発火
  4. APIへのフェッチリクエスト
  5. レスポンスを待機
  6. stateを更新
  7. 再レンダリング

結果:ユーザーに「Loading...」が1〜2秒間表示される

Server Componentモデル

// This runs on the server
import { db } from '@/lib/database'

export default async function UserProfile() {
  const user = await db.user.findFirst()

  return <div>{user.name}</div>
}

処理フロー

  1. リクエストがサーバーに到達
  2. コンポーネントがサーバーで実行される
  3. データベースクエリが実行される
  4. データを含んだHTMLがブラウザーに送信される
  5. ユーザーにコンテンツが即座に表示される

結果:ユーザーは即座にデータを閲覧できる(理論上は)

理想

Server Componentsは以下の問題の解決を目指すものでした:

  • ローディング状態
  • クライアントサイドのデータフェッチ
  • APIルートのボイラープレート
  • 巨大なJavaScriptバンドル

現実は、もっと複雑です


Part 2:落とし穴

問題点 1:暗黙のウォーターフォール

Server Componentsで実際に起こることを見てみましょう:

// app/dashboard/page.tsx
export default async function Dashboard() {
  const user = await getUser() // 200ms

  return (
    <div>
      <Header user={user} />
      <Stats userId={user.id} /> {/* Another server component */}
      <RecentActivity userId={user.id} /> {/* Another server component */}
    </div>
  )
}

// Stats component
async function Stats({ userId }) {
  const stats = await getStats(userId) // 300ms - WAITS for parent!
  return <div>{stats.total}</div>
}

// RecentActivity component  
async function RecentActivity({ userId }) {
  const activity = await getActivity(userId) // 250ms - WAITS for Stats!
  return <div>{activity.map(...)}</div>
}

期待する動作:並列リクエスト(最大300ms)

実際の動作:シーケンシャルなウォーターフォール

  1. getUser() - 200ms
  2. コンポーネントがレンダリングされ、<Stats>を検出
  3. getStats() - 300ms(ステップ1の後に開始)
  4. コンポーネントがレンダリングされ、<RecentActivity>を検出
  5. getActivity() - 250ms(ステップ3の後に開始)

合計時間:750ms(並列化されていない!)

なぜこうなるのか:Reactはコンポーネントをシーケンシャルにレンダリングします。各非同期コンポーネントが、次のコンポーネントをブロックするのです。

修正

手動で並列化しなければなりません:

export default async function Dashboard() {
  // Run all queries in parallel
  const [user, stats, activity] = await Promise.all([
    getUser(),
    getStats(),
    getActivity()
  ])

  return (
    <div>
      <Header user={user} />
      <Stats data={stats} /> {/* Now a regular component */}
      <RecentActivity data={activity} /> {/* Now a regular component */}
    </div>
  )
}

しかし、これでは以下の利点が失われます:

  • コンポーネントのカプセル化
  • 関心の分離
  • Server Componentsの目的

問題点 2:キャッシュというブラックボックス

React 19とNext.js 14以降は、積極的なキャッシュ機構を備えています。これは良いことのように聞こえますが、本番環境で問題を起こすまでは、です。

私たちが遭遇した実際のバグ

// app/posts/page.tsx
export default async function PostsPage() {
  const posts = await db.post.findMany()
  return <PostList posts={posts} />
}

起こったこと

  1. ユーザーが新しい投稿を作成
  2. /postsにリダイレクトされる
  3. 新しい投稿が表示されない
  4. ページをリロードしても無駄
  5. ブラウザーのキャッシュをクリアしても無駄

理由:Next.jsがサーバー上でデータベースのクエリ結果をキャッシュしていました。そして、そのキャッシュを無効化(invalidate)していなかったのです。

解決策

export const revalidate = 0 // Disable caching

export default async function PostsPage() {
  const posts = await db.post.findMany()
  return <PostList posts={posts} />
}

しかしながら

  • パフォーマンス上の利点が失われる
  • ページロードごとにデータベースにアクセスが走る
  • Client Componentsを使っていた頃のパフォーマンスに逆戻り

より深刻な問題:何がキャッシュされているのか確認できません。キャッシュインスペクターのようなものはありません。推測するしかないのです。


問題点 3:クライアントとサーバーの境界が分かりにくい

これが私たちのチームにとって最大の問題です:

// ❌ This looks like it should work
'use client'
import { ServerComponent } from './ServerComponent'

export default function ClientComponent() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <ServerComponent /> {/* Error! */}
    </div>
  )
}

エラー:「Server ComponentをClient Componentにインポートしています」

理由:ひとたび'use client'を使うと、その配下は全てClient Componentでなければならないからです。

修正

// ✅ Pass Server Component as children
'use client'

export default function ClientComponent({ children }) {
  const [count, setCount] = useState(0)

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      {children}
    </div>
  )
}

// In parent (Server Component)
<ClientComponent>
  <ServerComponent />
</ClientComponent>

これは直感的でないように見えます。経験の浅い開発者は、この仕様に何週間も苦しめられています。


問題点 4:フォームの複雑性

従来のフォームハンドリング:

'use client'

export default function Form() {
  async function handleSubmit(e) {
    e.preventDefault()
    const res = await fetch('/api/submit', {
      method: 'POST',
      body: JSON.stringify(formData)
    })
    if (res.ok) router.push('/success')
  }

  return <form onSubmit={handleSubmit}>...</form>
}

シンプルに機能し、誰もが理解できます

Server Actions(RSC流):

// app/actions.ts
'use server'

export async function submitForm(formData: FormData) {
  const name = formData.get('name')
  await db.user.create({ data: { name } })
  revalidatePath('/users')
  redirect('/success')
}

// Form component
export default function Form() {
  return (
    <form action={submitForm}>
      <input name="name" />
      <button type="submit">Submit</button>
    </form>
  )
}

問題点

  1. エラーハンドリングが不明確:どこでエラーをキャッチすればよいのでしょう?
  2. ローディング状態:どうやってスピナーを表示するのでしょう?
  3. バリデーション:クライアントサイドのバリデーションにはClient Componentが必要

「解決」にはuseFormStatusが必要です

'use client'
import { useFormStatus } from 'react-dom'
import { submitForm } from './actions'

function SubmitButton() {
  const { pending } = useFormStatus()
  return (
    <button disabled={pending}>
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  )
}

export default function Form() {
  return (
    <form action={submitForm}>
      <input name="name" />
      <SubmitButton />
    </form>
  )
}

このために必要なもの

  • Server Actions用の別ファイル
  • ボタン用のClient Component
  • 学習すべき新しいフック
  • 増えるファイルと複雑さ

以前の方法のシンプルさと比べて、どれほどのメリットがあるのでしょうか。


問題点 5:TypeScriptの型安全性が失われる

Server Componentsは、TypeScriptを巧妙なやり方で壊します:

// lib/db.ts
export async function getUser() {
  return await db.user.findFirst()
}

// app/page.tsx - Server Component
export default async function Page() {
  const user = await getUser()
  return <UserProfile user={user} /> // Type error!
}

// components/UserProfile.tsx - Client Component
'use client'
interface Props {
  user: User // Prisma type with Date objects
}
export default function UserProfile({ user }: Props) {
  return <div>{user.createdAt.toISOString()}</div> // Runtime error!
}

問題点:Server ComponentsはpropsをJSONにシリアライズします。Dateオブジェクトは文字列になってしまうのです。

TypeScriptはこれを型エラーとして検知できません。本番環境でランタイムエラーが発生します。

修正:手動でのシリアライズ

export async function getUser() {
  const user = await db.user.findFirst()
  return {
    ...user,
    createdAt: user.createdAt.toISOString() // Manual conversion
  }
}

このために必要なこと

  • データベース呼び出しごとのシリアライズ関数
  • サーバー用とクライアント用で別々の型定義
  • 安全のためのランタイムチェック

Part 3:Server Componentsがうまく機能するケース

ただ否定ばかりしたいわけではありません。Server Componentsがうまく機能する特定のユースケースもあります。

✅ ユースケース 1:静的コンテンツサイト

// Blog post page
export default async function BlogPost({ params }) {
  const post = await getPost(params.slug)

  return (
    <article>
      <h1>{post.title}</h1>
      <Markdown content={post.content} />
    </article>
  )
}

うまくいく理由

  • インタラクティビティーが不要
  • コンテンツの変更がまれ
  • キャッシュに最適
  • SEOに強い

結論:Server Componentsが輝ける場所です。

✅ ユースケース 2:ダッシュボードのレイアウト

export default async function DashboardLayout({ children }) {
  const user = await getCurrentUser()

  return (
    <div>
      <Sidebar user={user} />
      <main>{children}</main>
    </div>
  )
}

うまくいく理由

  • 全てのページでユーザーデータが必要
  • レイアウト部分のインタラクティビティーは最小限
  • ユーザーセッションをキャッシュできる

結論:良いユースケースです。

✅ ユースケース 3:データテーブル(フィルターなし)

export default async function UsersTable() {
  const users = await db.user.findMany()

  return (
    <table>
      {users.map(user => (
        <tr key={user.id}>
          <td>{user.name}</td>
          <td>{user.email}</td>
        </tr>
      ))}
    </table>
  )
}

うまくいく理由

  • 表示専用のデータ
  • クライアントサイドのstateが不要
  • サーバーサイドレンダリングの方が速い

結論:適切なユースケースです。


Part 4:Server Componentsが失敗するケース

❌ アンチパターン 1:リアルタイム更新

// ❌ This doesn't work
export default async function LiveFeed() {
  const posts = await getPosts()

  return <PostList posts={posts} />
}

問題点:更新をサブスクライブする方法がありません。WebSocketを利用するためにはClient Componentが必要です。

必要なもの:useEffectとWebSocket接続を持つClient Component。

Server Componentsはこういった場面では力を発揮できません


❌ アンチパターン 2:複雑なフォーム

// ❌ This gets messy fast
export default function MultiStepForm() {
  // How do you manage form state across steps?
  // How do you validate before submission?
  // How do you show field-level errors?
}

問題点:フォームはクライアントサイドのstateを必要とします。Server Actionsとクライアントのstateを混ぜるのは混乱のもとです。

解決策:制御された入力を持つClient Componentを使う。


❌ アンチパターン 3:インタラクティブ性の高いUI

// ❌ Server Components are wrong here
export default async function DataGrid() {
  const data = await getData()

  // Users need to:
  // - Sort columns
  // - Filter rows
  // - Select items
  // - Paginate

  return <Table data={data} />
}

問題点:全てのインタラクションでサーバーとのラウンドトリップが必要になります。

解決策:ローカルstateまたはTanstack Query(旧React Query)等を持つClient Component。


Part 5:Server Componentsの本当のコスト

Server Componentsの隠れたコストについて話しましょう。

コスト 1:開発者体験

Server Components以前

  • 経験の浅い開発者がチームに参加
  • Reactフックを学ぶ
  • クライアントサイドのデータフェッチを理解する
  • 1〜2週間で価値を発揮し始める

Server Components以後

  • 経験の浅い開発者がチームに参加
  • Reactフックを学ぶ
  • Server Componentsを学ぶ
  • クライアント/サーバーの境界ルールを学ぶ
  • Server Actionsを学ぶ
  • キャッシュの挙動を学ぶ
  • どのパターンをいつ使うべきか学ぶ
  • 1〜2カ月で生産的になる(運が良ければ)

私たちのチームの実際の統計:オンボーディング期間が2週間から6週間に増加しました。


コスト 2:デバッグの難しさ

Client Componentのバグ

  1. DevToolsを開く
  2. コンソールでエラーを確認
  3. ブレークポイントを追加
  4. コードをステップ実行
  5. バグを修正

所要時間:10〜30分

Server Componentのバグ

  1. エラーはターミナルに表示される(ブラウザーではない)
  2. ブラウザーのDevToolsが使えない
  3. console.log文を追加
  4. 問題を再現させる
  5. ターミナルのログを確認
  6. ステップ3〜6を何度も繰り返す
  7. 最終的にバグを発見

所要時間:1〜3時間


コスト 3:バンドルサイズ

理想:Server Components利用によるバンドルサイズ削減

現実の確認

Server Components以前(純粋なクライアント)

  • Reactバンドル:45KB
  • アプリコード:120KB
  • 合計:165KB

Server Components以後

  • Reactバンドル:45KB
  • React Server Componentsランタイム:28KB(new!)
  • アプリコード(クライアント部分):80KB
  • Server Actionボイラープレート:15KB
  • 合計:168KB

バンドルサイズ +3KB(1.8%)

しかし、待ってください、まだあります:

  • HTMLサイズの増加(サーバーでレンダリングされたコンテンツ)
  • ネットワークリクエストの増加(Server Componentツリー)
  • RSCペイロードのオーバーヘッド

実際の結果:初期バンドルは減少どころかわずかに増大し、総転送データ量は増加しました。


コスト 4:パフォーマンス(という驚き)

移行前後で測定しました:

メトリクス:Time to Interactive (TTI)

Server Components以前

  • ホームページ:1.2秒
  • ダッシュボード:1.8秒
  • 製品ページ:1.4秒

Server Components以後

  • ホームページ:1.9秒(58%悪化!)
  • ダッシュボード:2.4秒(33%悪化!)
  • 製品ページ:1.1秒(21%改善)

なぜ遅くなったのか?

  • サーバーレンダリングに時間がかかる
  • ウォーターフォールリクエスト(問題点 1を参照)
  • APIレスポンスのクライアントサイドキャッシュがない

なぜ製品ページは速くなったのか?

  • シンプルでデータ中心のページ
  • インタラクティビティーがない
  • RSCの完璧なユースケース

学び:Server Componentsは自動的に速くなるわけではありません。


Part 6:コミュニケーションの問題

私が最もフラストレーションを感じるのは、Reactチームがこれらの問題を認識していたことです

私の率直な印象:

  1. ウォーターフォール問題:Reactのドキュメントに記載があるがやや伝わりづらい
  2. キャッシュ問題:「より良いdevtoolsを開発中です」(2年間ずっと)
  3. TypeScript問題:「これは期待される動作です」
  4. デバッグの難しさ:「console.logを使ってください」(本気で?)

コミュニティーは、ドキュメントからではなく、本番環境でのつらい経験を通じてこれらの問題を発見しました。

以下と比較してみてください:

  • Svelte:優れたドキュメント、明確な制限事項
  • Vue:トレードオフについて正直
  • Solid:学習曲線について率直

Part 7:では、実際どうすべきか?

戦略 1:選択的導入(推奨)

Server Componentsを使うケース

  • 静的コンテンツ
  • シンプルなデータ表示
  • レイアウトコンポーネント
  • SEOが重要なページ

Client Componentsを使うケース

  • バリデーション付きのフォーム
  • リアルタイム機能
  • インタラクティブなUI
  • 複雑なstate管理

構成例

app/
  (marketing)/          # Server Components
    page.tsx
    about/page.tsx
  (dashboard)/          # Mixed
    layout.tsx         # Server Component
    page.tsx           # Client Component (interactive)
  (blog)/               # Server Components
    [slug]/page.tsx

戦略 2:ハイブリッドレンダリング

// Server Component (page)
export default async function ProductPage({ params }) {
  const product = await getProduct(params.id)

  // Render static content on server
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>

      {/* Interactive parts as Client Components */}
      <AddToCartButton productId={product.id} />
      <Reviews productId={product.id} />
    </div>
  )
}

// Client Component (interactive)
'use client'
function AddToCartButton({ productId }) {
  const [loading, setLoading] = useState(false)

  async function handleClick() {
    setLoading(true)
    await addToCart(productId)
    setLoading(false)
  }

  return <button onClick={handleClick}>Add to Cart</button>
}

これがうまくいく理由

  • サーバーが静的コンテンツをレンダリング
  • クライアントがインタラクティビティーを処理
  • 関心の分離が明確

戦略 3:待つ(議論の余地はあるが、妥当)

新しいプロジェクトを始める場合:

以下に該当するなら、まだServer Componentsを使わないことを検討してください

  • 小規模なチームである
  • 迅速なイテレーションが必要
  • アプリのインタラクティブ性が高い
  • 開発者体験を重視する

代わりに以下を使い続けてください

  • Pages Router(Next.js 12までの標準)
  • Tanstack Queryを使ったClient Components
  • 従来のAPIルート

理由:これらのパターンは:

  • ドキュメントが整備されている
  • よく理解されている
  • 実戦でテスト済み
  • デバッグが容易

Server Componentsはいずれ成熟します。エコシステムも改善されるでしょう。移行は後からでもできます。


Part 8:移行ガイド(どうしても移行する場合)

ステップ 1:アプリの棚卸し

全てのページを分類します:

✅ Good for RSC:
- Marketing pages
- Blog posts  
- Documentation
- Static dashboards

⚠️ Maybe:
- User profiles
- Product listings
- Search results

❌ Bad for RSC:
- Real-time chat
- Complex forms
- Canvas/drawing apps
- Admin panels with lots of interactivity

ステップ 2:小さく始める

全てを書き換えないでください。1種類のページタイプを選びます:

// Start with: Static blog posts
// app/blog/[slug]/page.tsx

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug)
  return <Article post={post} />
}

まずはシンプルなページでパターンを学びましょう。

ステップ 3:徐々にインタラクティビティーを追加する

// app/blog/[slug]/page.tsx (Server Component)
export default async function BlogPost({ params }) {
  const post = await getPost(params.slug)

  return (
    <article>
      <h1>{post.title}</h1>
      <Content>{post.content}</Content>

      {/* Client Component for interactions */}
      <LikeButton postId={post.id} />
      <Comments postId={post.id} />
    </article>
  )
}

Server Componentsは、データフェッチと静的コンテンツに集中させましょう。

ステップ 4:ウォーターフォールに注意する

React DevToolsのProfilerを使いましょう:

// ❌ Bad: Sequential
<ServerComponent1 />
<ServerComponent2 /> {/* Waits for 1 */}
<ServerComponent3 /> {/* Waits for 2 */}

// ✅ Good: Parallel
const [data1, data2, data3] = await Promise.all([
  getData1(),
  getData2(),
  getData3()
])

ステップ 5:適切なエラー境界(Error Boundary)を設定する

// app/error.tsx
'use client' // Error boundaries must be Client Components

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  )
}

Server Componentsは本番環境で失敗する可能性があるため、エラー境界が必要です。


Part 9:代替案

代替案 1:Client Components + Tanstack Queryを使い続ける

'use client'
import { useQuery } from '@tanstack/react-query'

export default function ProductPage({ params }) {
  const { data: product, isLoading } = useQuery({
    queryKey: ['product', params.id],
    queryFn: () => fetch(`/api/products/${params.id}`).then(r => r.json())
  })

  if (isLoading) return <Skeleton />

  return <ProductDetails product={product} />
}

メリット

  • よく理解されたパターン
  • 優れたDX
  • 強力なキャッシュ
  • 容易なデバッグ

デメリット

  • クライアントサイドのローディング状態
  • 大きめの初期バンドル
  • SEOには追加作業が必要

結論:多くのアプリにとって、いまだに素晴らしい選択肢です。


代替案 2:Remixに移行する

Remixには Next.js より前から(loader を通じて)Server Components に類似した仕組みがありました:

// routes/products/$id.tsx
export async function loader({ params }) {
  return json(await getProduct(params.id))
}

export default function Product() {
  const product = useLoaderData()
  return <ProductDetails product={product} />
}

メリット

  • よりシンプルなメンタルモデル
  • より整備されたドキュメント
  • 明確なデータローディングパターン
  • 優れたエラーハンドリング

デメリット

  • 異なるフレームワーク
  • 移行コスト

結論:新規プロジェクトでは検討する価値があります。


代替案 3:Astroとアイランドアーキテクチャ

---
// src/pages/product/[id].astro
const product = await getProduct(Astro.params.id)
---

<Layout>
  <h1>{product.name}</h1>
  <p>{product.description}</p>

  <AddToCartButton client:load productId={product.id} />
</Layout>

メリット

  • デフォルトで静的
  • オプトインでインタラクティブにできる
  • 優れたパフォーマンス
  • シンプルなメンタルモデル

デメリット

  • 純粋なReactではない
  • エコシステムが小さい

結論:コンテンツ中心のサイトには最適です。


Part 10:未来(これから)

Reactチームのロードマップ

最近のRFCや議論から:

  1. より良いDevTools - 「近日公開」(2年間聞き続けていますが)
  2. キャッシュの改善 - よりきめ細かな制御
  3. ストリーミングの改善 - Suspenseとのより良い統合
  4. TypeScriptサポート - Server Componentsの型サポート改善

私たちが本当に必要としているもの

  1. Server Componentsを使うべきでない時についての明確なドキュメント
  2. 実際のベンチマークを伴うパフォーマンスガイドライン
  3. RSCを安全に導入するための移行ツール
  4. 実際に機能するデバッグツール
  5. 制限についての正直なコミュニケーション

結論

React Server Componentsは銀の弾丸(決め手)ではありません。特定のユースケース、重大な複雑さ、そして現実的なトレードオフを伴うツールです。

現実

  • 一部のアプリには適しているが、他のアプリには適していない
  • メンタルモデルの大幅な転換が必要
  • ドキュメントが不十分
  • 本番環境での問題が頻発している
  • 学習曲線が険しい

私の率直なお勧め

Server Componentsを使うべきケース

  • ✅ コンテンツ中心のサイトを構築している
  • ✅ 複雑さを扱えるシニアチームがいる
  • ✅ アーリーアダプターであることをいとわない
  • ✅ 学習に時間を投資できる

Server Componentsを使うべきでないケース

  • ❌ アプリのインタラクティブ性が高い
  • ❌ 経験の浅い開発者が多い
  • ❌ 迅速な開発が必要
  • ❌ 安定性が最重要

Reactコミュニティーは、以下について率直に話し合う必要があります

  • RSCが助けになる時 vs ならない時
  • 本当のDXコスト
  • 実際のパフォーマンスへの影響
  • ドキュメントの不備

Server ComponentsはReactの未来です。しかし、一部のアプリケーションにとってはまだ先の話です。 賢明な選択を。


クイック意思決定フレームワーク

自問してみてください

①アプリの何%がインタラクティブですか?

  • 30%未満:Server Componentsを検討
  • 30〜70%:ハイブリッドアプローチを使用
  • 70%超:Client Componentsを堅持

②チームの経験レベルは?

  • 全員シニア:問題なし
  • 混合:慎重に進める
  • ほぼ経験が浅い開発者:待つ

③タイムラインは?

  • 学習プロジェクト:実験する
  • 厳しいデッドライン:避ける
  • 長期的な投資:検討の余地あり

④優先事項は?

  • パフォーマンス:まず測定する
  • DX:待つ方がよいかも
  • SEO:良いユースケース
  • 複雑さ:避ける

リソース

監修者
監修者_古川陽介
古川陽介
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
複合機メーカー、ゲーム会社を経て、2016年に株式会社リクルートテクノロジーズ(現リクルート)入社。 現在はAPソリューショングループのマネジャーとしてアプリ基盤の改善や運用、各種開発支援ツールの開発、またテックリードとしてエンジニアチームの支援や育成までを担う。 2019年より株式会社ニジボックスを兼務し、室長としてエンジニア育成基盤の設計、技術指南も遂行。 Node.js 日本ユーザーグループの代表を務め、Node学園祭などを主宰。