🐧

Next.js App RouterにおけるAWS Amplifyの実装パターンと実行コンテキストの影響

に公開

はじめに

以下はAmplify でのAppSyncによるServer Action関数の実装時に「非認証ユーザー」に対してどのようなAPIへのアクセスを行わせるか?という場面で認証のエラーが発生しその回避と実装の切り分けの考え方をまとめたものです。Cursorを使って実装を進めていく中で方針を立てたことをCursorにまとめてもらっていますが、文章についてはこちらで確認をしています。というわけで以下解説。

ーーーーここからーーーー

Next.jsのApp RouterとAWS Amplifyを組み合わせて使用する際、特にサーバーサイドでの実装において、実行コンテキストの違いが重要な影響を与えます。本記事では、実際のプロジェクトでの経験を基に、最適な実装パターンと実行コンテキストの影響について解説します。

実行コンテキストの違いとAmplify設定の継承

Next.jsのApp Routerでは、以下の3つの主要な実行コンテキストが存在します:

  1. サーバーコンポーネント
  2. サーバーアクション('use server'
  3. クライアントコンポーネント

これらのコンテキストは、AWS Amplifyの設定の適用方法に大きく影響します。

サーバーコンポーネントでの実装

// layout.tsx
import { Amplify } from 'aws-amplify'
Amplify.configure(config, { ssr: true })

// page.tsx
const client = generateClient({ authMode: 'iam' })
// トップのlayout.tsxの設定を継承するため、
// 追加のAmplify.configureは不要

外部ファイル化した場合

// actions/doOperation.ts
const client = generateClient({ authMode: 'iam' })
// 独立したモジュールとして実行されるため、
// Amplify.configureが必要

// または
import { publicClient } from '@/utils/amplifyServerUtils'
// 一元管理されたクライアントを使用

実行コンテキストによる違いの詳細

1. サーバーコンポーネント内での直接実装

// page.tsx
const publicClient = generateClient({
  authMode: 'iam',
})

const doOperation = async () => {
  const result = await publicClient.graphql({...})
}
  • トップのlayout.tsxの設定を継承
  • 追加のAmplify.configureは不要
  • サーバーコンポーネントのコンテキスト内で実行

2. 外部ファイル化した場合

// actions/doOperation.ts
const publicClient = generateClient({
  authMode: 'iam',
})
// エラー: "No GraphQL endpoint configured in Amplify.configure()"

// 解決策1: 追加の設定
import { Amplify } from 'aws-amplify'
Amplify.configure(config, { ssr: true })

// 解決策2: 一元管理されたクライアントを使用
import { publicClient } from '@/utils/amplifyServerUtils'

推奨される実装パターン

1. クライアントの一元管理

// utils/amplifyServerUtils.ts
import { Amplify } from 'aws-amplify'
import { generateClient } from 'aws-amplify/api'
import { generateServerClientUsingCookies } from '@aws-amplify/adapter-nextjs/api'

// 認証が必要な操作用
export const cookieBasedClient = generateServerClientUsingCookies({
  config,
  cookies,
})

// 認証が不要な操作用
Amplify.configure(config, { ssr: true })
export const publicClient = generateClient({
  authMode: 'iam',
})

2. 実装パターンの選択

サーバーコンポーネント内での直接実装

  • 認証が不要な操作
  • トップのレイアウト設定を継承
  • シンプルな実装

外部ファイル化した実装

  • 共通のロジックを再利用
  • 一元管理されたクライアントを使用
  • 設定の重複を避ける

実装方針のまとめ

  1. APIアクセスの実装

    • 認証が必要な操作:サーバーアクションとして実装
    • 認証が不要な操作:サーバーコンポーネント内で直接実装、または一元管理されたクライアントを使用
  2. クライアントの使い分け

    • cookieBasedClient:認証が必要な操作
    • publicClient:認証が不要な操作
  3. 設定の管理

    • クライアントの生成と設定を一元管理
    • 実行コンテキストに応じた適切な設定の適用

注意点

  1. サーバーアクションは独立した実行コンテキストを持つ
  2. サーバーコンポーネントはトップのレイアウト設定を継承
  3. 外部ファイル化した場合は、設定の適用方法に注意
  4. クライアントの生成タイミングと設定の適用タイミングに注意

まとめ

Next.jsのApp RouterとAWS Amplifyを組み合わせる際は、実行コンテキストの違いを理解し、適切な実装パターンを選択することが重要です。特に、外部ファイル化した場合の設定の適用方法に注意を払い、一元管理されたクライアントを使用することで、より保守性の高いコードを実現できます。

ーーーーここまでーーーー

というわけで、実際には「fetchするだけの関数ならServer Actionにする意味ないのでは?」などあると思うんですが、外部ファイル化する時点でAmplify.configureを必要とする点やAPIへのアクセスの管理で方法論が分かれることで混乱をするケースがありそうなのでこのような方針にしています。

というわけで、Amplifyで100万超えたユーザー数のウェブサービスを運用保守してるんですが、比較的Amplifyの箱庭の中でやれることは多いので、何かご依頼があれば気軽に こちらまで にご連絡ください。その際はこの記事を読んだよ、と教えてくれると話が早いと思います。

Discussion