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つの主要な実行コンテキストが存在します:
- サーバーコンポーネント
- サーバーアクション(
'use server'
) - クライアントコンポーネント
これらのコンテキストは、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. 実装パターンの選択
サーバーコンポーネント内での直接実装
- 認証が不要な操作
- トップのレイアウト設定を継承
- シンプルな実装
外部ファイル化した実装
- 共通のロジックを再利用
- 一元管理されたクライアントを使用
- 設定の重複を避ける
実装方針のまとめ
-
APIアクセスの実装
- 認証が必要な操作:サーバーアクションとして実装
- 認証が不要な操作:サーバーコンポーネント内で直接実装、または一元管理されたクライアントを使用
-
クライアントの使い分け
-
cookieBasedClient
:認証が必要な操作 -
publicClient
:認証が不要な操作
-
-
設定の管理
- クライアントの生成と設定を一元管理
- 実行コンテキストに応じた適切な設定の適用
注意点
- サーバーアクションは独立した実行コンテキストを持つ
- サーバーコンポーネントはトップのレイアウト設定を継承
- 外部ファイル化した場合は、設定の適用方法に注意
- クライアントの生成タイミングと設定の適用タイミングに注意
まとめ
Next.jsのApp RouterとAWS Amplifyを組み合わせる際は、実行コンテキストの違いを理解し、適切な実装パターンを選択することが重要です。特に、外部ファイル化した場合の設定の適用方法に注意を払い、一元管理されたクライアントを使用することで、より保守性の高いコードを実現できます。
ーーーーここまでーーーー
というわけで、実際には「fetchするだけの関数ならServer Actionにする意味ないのでは?」などあると思うんですが、外部ファイル化する時点でAmplify.configureを必要とする点やAPIへのアクセスの管理で方法論が分かれることで混乱をするケースがありそうなのでこのような方針にしています。
というわけで、Amplifyで100万超えたユーザー数のウェブサービスを運用保守してるんですが、比較的Amplifyの箱庭の中でやれることは多いので、何かご依頼があれば気軽に こちらまで にご連絡ください。その際はこの記事を読んだよ、と教えてくれると話が早いと思います。
Discussion