Next.js App Router (サーバーコンポーネント)
このクイックスタートガイドでは、TypeScript、Next.js App Router とサーバーコンポーネント、および React を使用してタスクリストアプリケーションを構築する方法を説明します。これらのテクノロジーが初めての場合は、まず公式の React、Next.js、TypeScript のチュートリアルを確認することをお勧めします。
Prerequisites
Before you get started, make sure you have the following installed:
You will also need to create an AWS account. Note that AWS Amplify is part of the AWS Free Tier.
Create a project
First, you will need to create a new Next.js app. The following command will create a Next.js app in a directory called next-amplify-gen2 that uses the App Router.
npm create next-app@14 -- next-amplify-gen2 --typescript --eslint --app --no-src-dir --no-tailwind --import-alias '@/*'cd next-amplify-gen2The easiest way to get started with AWS Amplify is through npm with create-amplify.
npm create amplify@latest? Where should we create your project? (.) # press enterRunning this command will scaffold a lightweight Amplify project in your current project with the following files:
├── amplify/│ ├── auth/│ │ └── resource.ts│ ├── data/│ │ └── resource.ts│ ├── backend.ts│ └── package.json├── node_modules/├── .gitignore├── package-lock.json├── package.json└── tsconfig.jsonStart local dev server
Let's start a local dev server for your app development. For the frontend, run npm run dev to spin up a localhost dev server with the default Next.js template.
npm run devFor the backend, we're going to start a cloud sandbox environment. Amplify gives every developer a personal cloud sandbox environment that provides isolated development spaces to rapidly build, test, and iterate. When you're working with a team, each developer will have their own personal cloud sandbox. In a new terminal window, run the following command:
npx ampx sandboxYou should now have these two commands running concurrently in different terminal windows.
Build a backend
Next, we will add data and auth capabilities to the app. In Amplify Gen 2, the resource.ts files are where you define the corresponding backend resource and details about it.
Add data
create-amplify provides the scaffolding of a amplify/data/resource.ts file, which is ready to deploy.
See the complete amplify/data/resources.ts
Open the amplify/data/resource.ts file in your text editor, and you will see a default data model generated for you.
Note: The steps defined in the code below do not require action to complete this quickstart. These notes are provided for every new app and will help you when you build your next app on your own.
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
/*== STEP 1 ===============================================================The section below creates a Todo database table with a "content" field. Tryadding a new "isDone" field as a boolean. The authorization rules belowspecify that owners, authenticated via your Auth resource, can "create","read", "update", and "delete" their own records. Public users,authenticated via an API key, can only "read" records.=========================================================================*/const schema = a.schema({ Todo: a .model({ content: a.string() }) .authorization(allow => [allow.owner(), allow.publicApiKey().to(['read'])])});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({ schema, authorizationModes: { defaultAuthorizationMode: 'apiKey', // API Key is used for allow.publicApiKey() rules apiKeyAuthorizationMode: { expiresInDays: 30 } }});
/*== STEP 2 ===============================================================Go to your frontend source code. From your client-side code, generate aData client to make CRUDL requests to your table. (THIS SNIPPET WILL ONLYWORK IN THE FRONTEND CODE FILE.)
Using JavaScript or Next.js React Server Components, Middleware, ServerActions, or Pages Router? Review how to generate Data clients for those usecases: https://docs.amplify.aws/react/frontend/data/connect-to-API/=========================================================================*/
/*"use client"import { generateClient } from "aws-amplify/data";import { type Schema } from "@/amplify/data/resource";
const client = generateClient<Schema>() // use this Data client for CRUDL requests*/
/*== STEP 3 ===============================================================Fetch records from the database and use them in your frontend component.(THIS SNIPPET WILL ONLY WORK IN THE FRONTEND CODE FILE.)=========================================================================*/
/* For example, in a React component, you can use this snippet in your function's RETURN statement */// const { data: todos } = client.models.Todo.list()
// return <ul>{todos.map(todo => <li key={todo.id}>{todo.content}</li>)}</ul>The schema generated by Amplify is for a to-do app. A schema is a blueprint
for how our app's data will be organized. Within the schema, we will define
models that will correspond to a database table—Todo in the above code.
Finally, we will define fields, which are attributes that each data instance
will have—in the generated code, the field is content. Each
field will have a type attached to it—in the above examples, we are stating
that the content field is a string.
Step 1: Open amplify/data/resource.ts and update it to add a done field of type boolean and a priority field of enum with a value of ['low', 'medium', 'high']. We've removed the default comments to shorten the code below for the next few examples.
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
// ...
const schema = a.schema({ Todo: a .model({ content: a.string(), done: a.boolean(), priority: a.enum(['low', 'medium', 'high']) }) .authorization(allow => [allow.owner(), allow.publicApiKey().to(['read'])]),});
// ...Once you save your changes to the data model, they will be deployed in seconds to your cloud sandbox.
The Todo data model is defined with authorization rules to allow the person who creates the Todo instance (the owner) to perform all actions on the data they own. We are also allowing all page viewers, including unauthenticated users, to read data.
Step 2: Remove public access by deleting the allow.publicApiKey().to(['read']) authorization rule. Your authorization rule will look like the code below:
// ...
.authorization(allow => [allow.owner()]),
// ...Below the schema declaration, you will see the defineData function, which receives our schema and authorization configuration as arguments. The default configuration is for an apiKey to enable public access.
Step 3: Update the defaultAuthorizationMode to userPool so that the default is to use user authentication.
// ...
export const data = defineData({ schema, authorizationModes: { defaultAuthorizationMode: 'userPool' }});Add authentication
Now let's work on our authentication configuration. Similar to the data/resource.ts we just worked on, the auth/resource.ts file has code to define our authentication configuration. In this case, we are setting the authentication method to log in with email.
See the complete amplify/auth/resources.ts
import { defineAuth } from '@aws-amplify/backend';
/** * Define and configure your auth resource * When used alongside data, it is automatically configured as an auth provider for data * @see https://docs.amplify.aws/gen2/build-a-backend/auth */export const auth = defineAuth({ loginWith: { email: true, // add social providers externalProviders: { /** * first, create your secrets using `amplify sandbox secret` * then, import `secret` from `@aws-amplify/backend` * @see https://docs.amplify.aws/gen2/deploy-and-host/sandbox-environments/features/#setting-secrets */ // loginWithAmazon: { // clientId: secret('LOGINWITHAMAZON_CLIENT_ID'), // clientSecret: secret('LOGINWITHAMAZON_CLIENT_SECRET'), // } } }, /** * enable multifactor authentication * @see https://docs.amplify.aws/gen2/build-a-backend/auth/manage-mfa */ // multifactor: { // mode: 'OPTIONAL', // sms: { // smsMessage: (code) => `Your verification code is ${code}`, // }, // }, userAttributes: { /** request additional attributes for your app's users */ // profilePicture: { // mutable: true, // required: false, // }, }});Let's customize the subject of the verification email sent to users after they sign up for our app. There is only one step to complete.
Open amplify/auth/resource.ts and update it to add a subject line by defining an object with email authentication properties referencing the code below:
// amplify/auth/resource.tsimport { defineAuth } from '@aws-amplify/backend';
export const auth = defineAuth({ loginWith: { email: { verificationEmailSubject: 'Welcome! Verify your email!' }, }});import { defineBackend } from '@aws-amplify/backend';import { auth } from './auth/resource';import { data } from './data/resource';
defineBackend({ auth, data});UI を構築する
バックエンドデータと認証リソースに接続する UI を追加しましょう。
Amplify クライアント側を構成する
まず、Amplify UI コンポーネントライブラリをインストールします:
npm add @aws-amplify/ui-react次に、プロジェクトのルートに components フォルダを作成し、以下の内容を ConfigureAmplify.tsx というファイルにコピーします。
// components/ConfigureAmplify.tsx"use client";
import { Amplify } from "aws-amplify";
import outputs from "@/amplify_outputs.json";
Amplify.configure(outputs, { ssr: true });
export default function ConfigureAmplifyClientSide() { return null;}app/layout.tsx を更新して、<ConfigureAmplifyClientSide /> をインポートしてレンダリングします。このクライアントコンポーネントは、アプリケーション内のクライアントページの Amplify を構成します。
// app/layout.tsximport "@aws-amplify/ui-react/styles.css";import type { Metadata } from "next";import { Inter } from "next/font/google";import "./globals.css";
import ConfigureAmplifyClientSide from "@/components/ConfigureAmplify";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app",};
export default function RootLayout({ children,}: { children: React.ReactNode;}) { return ( <html lang="en"> <body className={inter.className}> <ConfigureAmplifyClientSide /> {children} </body> </html> );}Amplify サーバー側を構成する
まず、Amplify Next.js アダプターをインストールします:
npm add @aws-amplify/adapter-nextjs次に、プロジェクトのルートから utils/amplify-utils.ts ファイルを作成し、以下のコードを貼り付けます。runWithAmplifyServerContext、cookiesClient、および AuthGetCurrentUserServer はここで宣言され、サーバーから Amplify アセットにアクセスするために使用されます。
// utils/amplify-utils.tsimport { cookies } from "next/headers";
import { createServerRunner } from "@aws-amplify/adapter-nextjs";import { generateServerClientUsingCookies } from "@aws-amplify/adapter-nextjs/api";import { getCurrentUser } from "aws-amplify/auth/server";
import { type Schema } from "@/amplify/data/resource";import outputs from "@/amplify_outputs.json";
export const { runWithAmplifyServerContext } = createServerRunner({ config: outputs,});
export const cookiesClient = generateServerClientUsingCookies<Schema>({ config: outputs, cookies,});
export async function AuthGetCurrentUserServer() { try { const currentUser = await runWithAmplifyServerContext({ nextServerContext: { cookies }, operation: (contextSpec) => getCurrentUser(contextSpec), }); return currentUser; } catch (error) { console.error(error); }}サーバー認証ルートを追加する
まず、withAuthenticator でラップされる components フォルダに、クライアント側の Login コンポーネントを作成します。ユーザーがログインしている場合、インデックスルートにリダイレクトされます。それ以外の場合、Amplify UI Authenticator コンポーネントがレンダリングされます。
// components/Login.tsx"use client";
import { withAuthenticator } from "@aws-amplify/ui-react";import { AuthUser } from "aws-amplify/auth";import { redirect } from "next/navigation";import { useEffect } from "react";
function Login({ user }: { user?: AuthUser }) { useEffect(() => { if (user) { redirect("/"); } }, [user]); return null;}
export default withAuthenticator(Login);次に、Login コンポーネントをレンダリングするための app/login/page.tsx 下に新しいルートを作成します。
// app/login/page.tsx
import Login from "@/components/Login";
export default function LoginPage() { return <Login />;}カスタム <Authenticator> 例
アプリケーションの中には、<Authenticator> コンポーネントに対してより多くのカスタマイズが必要な場合があります。次の例は、<Authenticator> にカスタムヘッダーを追加する方法を示しています。このような場合、components フォルダの Login ファイルは必要ありませんが、app/login/ フォルダの page ファイルですべて実行できます。その他のカスタマイズオプションについては、Authenticator カスタマイズを参照してください。
// app/login/page.tsx - Custom <Authenticator>
"use client";
import { Authenticator, Text, View, useAuthenticator,} from "@aws-amplify/ui-react";import { redirect } from "next/navigation";import { useEffect } from "react";
const components = { Header() { return ( <View textAlign="center"> <Text><span style={{color: "white"}}>Authenticator Header</span></Text> </View> ); },};
function CustomAuthenticator() { const { user } = useAuthenticator((context) => [context.user]);
useEffect(() => { if (user) { redirect("/"); } }, [user]);
return <Authenticator components={components} />;}
export default function Login() { return ( <Authenticator.Provider> <CustomAuthenticator /> </Authenticator.Provider> );}最後に、後で使用する Logout コンポーネントを作成します。
// components/Logout.tsx
"use client";
import { signOut } from "aws-amplify/auth";import { useRouter } from "next/navigation";
export default function Logout() { const router = useRouter();
return ( <button onClick={async () => { await signOut(); router.push("/login"); }} className="px-2 bg-white text-black" > Sign out </button> );}サーバー側のリダイレクト用のミドルウェアを追加する
以下の内容で、プロジェクトのルートに middleware.ts を作成します。
このミドルウェアは runWithAmplifyServerContext でラップされた fetchAuthSession を実行し、ユーザーがログインしていない場合は /login にリダイレクトします。
// middleware.tsimport { NextRequest, NextResponse } from "next/server";
import { fetchAuthSession } from "aws-amplify/auth/server";
import { runWithAmplifyServerContext } from "@/utils/amplify-utils";
export async function middleware(request: NextRequest) { const response = NextResponse.next();
const authenticated = await runWithAmplifyServerContext({ nextServerContext: { request, response }, operation: async (contextSpec) => { try { const session = await fetchAuthSession(contextSpec, {}); return session.tokens !== undefined; } catch (error) { console.log(error); return false; } }, });
if (authenticated) { return response; }
return NextResponse.redirect(new URL("/login", request.url));}
export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - api (API routes) * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) * - login */ "/((?!api|_next/static|_next/image|favicon.ico|login).*)", ],};npm run dev でアプリケーションを実行し、http://localhost:3000 にアクセスします。オーセンティケーターが表示され、既に構成され、最初のサインアップの準備ができています。新しいユーザーアカウントを作成し、メール経由でアカウントを確認してからサインインします。
To-do アイテムのリストを表示する
次に、アプリのフロントエンドにデータを表示しましょう。
以下のコードは cookiesClient を使用して、バックエンドで定義された Todo モデルにアクセスします。
アプリのホームページファイル app/page.tsx を以下のコードで変更します:
// app/page.tsx
import { cookiesClient } from "@/utils/amplify-utils";
async function App() { const { data: todos } = await cookiesClient.models.Todo.list();
return ( <> <h1>Hello, Amplify 👋</h1> <ul> {todos && todos.map((todo) => <li key={todo.id}>{todo.content}</li>)} </ul> </> );}
export default App;ファイルを保存して http://localhost:3000 に戻ると、「Hello, Amplify」が表示され、現在のところ to-do のリストが空であるため、ページは空白になります。
新しい to-do アイテムを作成する
コンポーネントを更新して、新しい to-do リストアイテムを作成するためにユーザーにタイトルを促すフォームを持たせ、フォーム送信時に addTodo メソッドを実行します。本番アプリでは、Todo モデルの追加フィールドがフォームに追加されます。
to-do を作成した後、revalidatePath が実行され、このルートの Next.js キャッシュがクリアされ、ページの完全なリロードなしにサーバーの結果が即座に更新されます。
// app/page.tsx
import { revalidatePath } from "next/cache";
import { AuthGetCurrentUserServer, cookiesClient } from "@/utils/amplify-utils";
import Logout from "@/components/Logout";
async function App() { const user = await AuthGetCurrentUserServer(); const { data: todos } = await cookiesClient.models.Todo.list();
async function addTodo(data: FormData) { "use server"; const title = data.get("title") as string; await cookiesClient.models.Todo.create({ content: title, done: false, priority: "medium", }); revalidatePath("/"); }
return ( <> <h1>Hello, Amplify 👋</h1> {user && <Logout />} <form action={addTodo}> <input type="text" name="title" /> <button type="submit">Add Todo</button> </form>
<ul> {todos && todos.map((todo) => <li key={todo.id}>{todo.content}</li>)} </ul> </> );}
export default App;開発サーバーを終了する
ブラウザで localhost にアクセスして、ログインしたり、to-do を作成・リスト化できることを確認しましょう。フロントエンド開発サーバーとクラウドサンドボックスをシャットダウンして、開発セッションを終了できます。サンドボックスはバックエンドリソースを削除するよう促します。バックエンドを保持することはできますが、次回きれいに開始できるようにすべてのリソースを削除することをお勧めします。
Deploy and host a fullstack branch
Now that your app is working, let's deploy it to a shared fullstack branch so you can share the project with your team. Amplify offers a fully managed hosting service with CI/CD built in, making it easy to set up production and staging environments with Git branches. In Gen 2, every Git branch in your repository maps 1:1 to a fullstack branch in Amplify.
Create remote Git repository
If you already have your project remotely hosted in a Git repository, you can skip this step. Otherwise, navigate to your preferred Git provider, and add your project to a new repository. Amplify supports GitHub, AWS CodeCommit, GitLab, and Bitbucket.
Connect branch in the Amplify console
- To get started with Gen 2, log in to the Amplify console and navigate to your preferred AWS Region. (The Amplify console is available in 19 AWS Regions).
- From the Public Preview banner, choose Try Amplify Gen 2.
-
In the Git provider screen, choose Option 2: Start with an existing app. Connect the repository you just deployed and pick a branch.
-
Review all of your settings to ensure everything is set up correctly. Choose Save and deploy to deploy your web app.
Manage fullstack branch
The new Amplify console gives you a central place to manage your branches, hosting settings, CI/CD builds, and backend resources. Even though you can access backend resources directly from AWS service consoles, the Amplify console offers built-in user and data administration.