Amazon Web Services ブログ

TypeScript と Amplify JavaScript v6 を利用して Next.js アプリを構築する

AWS Amplify JavaScript Library の v6 の一般公開を発表できることを嬉しく思います。このリリースには、コミュニティから要望の多かった改善点や機能が多数含まれています。このリリースでは、バンドルサイズが大幅に縮小され、TypeScript のカバレッジと型サポートが強化され、セキュアランタイムトークンのサポートが強化され、Next.js App Router と Server Actions が完全にサポートされます。

より速いアプリのロード時間とより小さいバンドルサイズ

スピードは贅沢品ではなく、必需品です。そのため、依存関係の削減、Tree shaking 機能の向上、アーキテクチャの最適化に投資してきました。バンドルが小さいほどアプリケーションの読み込みが速くなり、高速ブロードバンド接続でも接続が不安定でも、ユーザーの関心と満足度を維持できます。これらの変更により、Amplify は最も一般的に使用されるフレームワークとビルドツールに最適化されました。

create-react-app を使用して新しい React アプリを構築し、カテゴリ内のすべての API のバンドルサイズを比較すると、v5 と比較してバンドルサイズが次のように減少していることがわかります。

Auth: 55kb to 32kb (42%)
Storage: 38kb to 21kb (45%)
Analytics (Pinpoint): 31kb to 18kb (42%)
Notifications and Analytics: 39kb to 23kb (41%)
API REST & GraphQL: 91kb to 38kb (58%)

AWS Amplify JS V6 Libraryのバンドルサイズ削減グラフ

注意: バンドルサイズ数値は、gzip 圧縮した最終的なバンドルサイズを計測したものです。削減後の結果は Amplify JavaScript v6.0.2 を使用しており、削減前の結果は、軽量クライアントが導入された Amplify JavaScript v5.3.4 を使用しています。これらの改善以前の例として、グラフには Amplify JavaScript v5.2.4 の生成結果も合わせて表記しています。

Amplify JS v6 では、機能単位の API 導入とツリーシェイク機能の改善により、アプリにインポートした API のみがバンドルサイズに影響し、未使用の機能は Tree Shake から除外されます。例えば、アプリで Auth パッケージのいくつかの API のみを使い (signInWithRedirect, signOut, fetchAuthSessiongetCurrentUser) ストレージでは (uploadData, downloadData)のみを使った場合、v6 ライブラリでは v5 ライブラリと比較して 59% のバンドルサイズを削減します(77kb→31kb)。

Auth Hosted UI と Storage を利用した際のバンドルサイズ比較グラフ

TypeScript エクスペリエンスの向上

TypeScript は、大規模で複雑なプロジェクトを管理しやすくする型安全性を提供しており、多くのチームの開発ワークフローに欠かせないものとなっています。Amplify の JavaScript Library における全ての公開 API に、使いやすく直感的な型が追加されました。これらの TypeScript 機能強化により、テキストエディタのシンタックスハイライトとコード補完がより充実したものになります。型チェックは、アプリを実行する前にいくつかのバグを特定するのに役立ちます。

それでは、新しい GenerateClient API を使用して、AWS AppSync API から製品をクエリする方法を見てみましょう。この例では、 GenerateClient API を使用して mutation を実行し、アプリ内の To Do を更新します。graphql API に対して型を設定する必要がなくなったことに気付くかもしれません。データ項目は自動的に推論されるようになっています。

graphql コマンド

我々は、ログインしているユーザーに対してはもっと良い型定義が必要だというフィードバックをいただきました。そこで、完全な user オブジェクトを返す新しい getCurrentUser API を作成しました。これを使って、signInDetails の下にある loginId を取得してみましょう。

userのフィールドを推測する

Next.js の App Router, API routes, そして middleware のサポート

Amplify JavaScript Library は、新しい Next.js アダプター により Next.js 機能をすべてサポートするようになりました。これにより、サーバーサイドレンダリングや、App Router で React Server Components を使用したり、middleware で API ルートを使用して認証されたユーザーのみへのアクセスを制御したりすることができます。Amplify JavaScript v6 を使用すると、任意の Next.js ランタイムで Amplify を実行し、任意のレンダリングオプション (SSR、ISR、または静的出力) を使用できます。

Next.js アダプターを使用すると、「Amplify サーバーコンテキスト」内で Amplify Library を実行できます。これにより、クラウドで Amplify Library の各機能を安全に使用できます。Amplify の機能の実行が終了した場合、コンテキストは完全に破棄され、クロスリクエスト汚染を排除することで、アプリのセキュリティ体制を強化します。

Next.js の App Router と Pages Router を使用するシナリオをいくつか見てみましょう。

前提条件

Next.js アダプターを含む最新の Amplify Library をインストールして開始します。

npm i aws-amplify @aws-amplify/adapter-nextjs

Amplify getting started guide をまだ読んでいない場合、まずはこのガイドを読んでください。 このチュートリアルの終了までに、GraphQL API と auth API のセットアップをしておく必要があります。

App Router

最初のシナリオでは、Next.js の App Router を利用することを想像しましょう。我々は、サーバーコンポーネント のうち 1 つを AWS AppSync API と接続し、いくつかのデータを一覧します。

まず、serverClient 関数を含む新しいユーティリティファイルを作成して、サーバー側で Amplify API と通信できるようにします。

// utils/server-utils.ts
import { cookies } from "next/headers";
import { generateServerClientUsingCookies } from "@aws-amplify/adapter-nextjs/api";

import config from "../../amplifyconfiguration.json";

export const serverClient = generateServerClientUsingCookies({
  config,
  cookies,
});

GenerateServerClientUsingCookie 関数は、動的レンダリング機能を備えた Next.js サーバーコンポーネントで使用できる API を生成します。これにより、サーバー側の Amplify API に安全にアクセスできるようになります。serverClient 関数はエクスポートされ、API を呼び出すユーティリティ関数として使用できます。page.tsx ファイルにこの関数をインポートし、それを使用して ToDo アプリ用のデータを一覧表示します。

// page.tsx
import { serverClient } from "@/utils/server-utils";
import * as query from "@/graphql/queries";

export default async function Home() {
  const { data, errors } = await serverClient.graphql({
    query: query.listTodos,
  });

  if(errors){
   // handle errors
  }

return (
    <div>
      {data.listTodos.items.map((post) => {
        return (
          <li key={post.id}>
            <div>Name: {post.name}</div>
            <span>Description: {post.description}</span>
          </li>
        );
      })}
    </div>
  );

ユーザーが投稿を削除できるように、削除機能を追加してみましょう。

// page.tsx
import * as mutations from "@/graphql/mutations";
import { serverClient } from "@/utils/server-utils";
...

async function deletePost(formData: FormData) {
    "use server";
    const id = formData.get("postId")?.toString();

    if (id) {
      const { errors } = await serverClient.graphql({
        query: mutations.deleteTodo,
        variables: {
          input: {
            id,
          },
        },
      });
      if (errors) {
        // handle errors
      }
    }
  }

この削除アクションは、Server Actions を使用して postID を取得し、それを削除します。これにより、サーバー上でフォームを送信し、ID を取得して入力変数に渡すことができます。

TypeScript の正しい推論を行うために、型生成に言及する必要があります。これには、src フォルダーに生成される api.ts ファイルと、graphql フォルダーにある mutations ファイルが含まれます。これを行うには、ルートフォルダで amplify add code コマンドを実行します。

Pages Router

もし、Next.js の Pages Router を使う場合は、 createServerRunner 関数と generateServerClientUsingReqRes 関数を使い、サーバー上でクエリを実行する必要があります。

まず、2 つの関数を含む utils ファイルを作成することから始めましょう。

  • runWithAmplifyServerContext 関数は、Amplify API をサーバー上で独立して実行するために使用されます。
  • serverGraphQLClient middleware, API routes, getServerSideProps もしくは getStaticProps で GraphQL API と通信するために使用されます
// utils/server-utils.ts
import { createServerRunner } from "@aws-amplify/adapter-nextjs";
import config from "../../amplifyconfiguration.json";
import { generateServerClientUsingReqRes } from "@aws-amplify/adapter-nextjs/api";

export const { runWithAmplifyServerContext } = createServerRunner({
  config,
});

export const serverGraphQLClient = generateServerClientUsingReqRes({
  config,
});

それでは、GetServerSideProps を使ってこれがどのように機能するのかを見てみましょう。サインインしているユーザーの情報を取得するシナリオを想定します。

// index.tsx
import { runWithAmplifyServerContext } from "@/utils/server-utils";
import { getCurrentUser } from "@aws-amplify/auth/server";

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
  const currentUser = await runWithAmplifyServerContext({
    nextServerContext: { request: req, response: res },
    operation: async (contextSpec) => getCurrentUser(contextSpec),
  });

  return { props: { currentUser } };
};

上記のように、クライアント側で getCurrentUser を使用してサインインしたユーザー情報を取得できます。ただし、インポートパスが "aws-amplify/auth/server" という少し異なるバージョンを使用して、サーバー側でもこれと同じ情報を取得できます。このサーバーバージョンの GetCurrentUser では、contextSpec を渡す必要があります。

別のシナリオでは、ServerGraphQLClient 関数を使ってタスクのリストを取得することもできます。

// index.tsx
import { listTodos } from "@/graphql/queries";
import {
  runWithAmplifyServerContext,
  serverGraphQLClient,
} from "@/utils/server-utils";

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
  const todoList = await runWithAmplifyServerContext({
    nextServerContext: { request: req, response: res },
    operation: (contextSpec) =>
      serverGraphQLClient.graphql(contextSpec, {
        query: listTodos,
      }),
  });

  return { props: { todoList } };
};

ServerGraphQLClientContextSpec を取り込む graphql API を使用します。ここでは、todo のリストを gql 形式で渡すこともできます。

Middleware

Amplify は Next.js の middleware もサポートするようになりました。ミドルウェア内の RunWithAmplifyServerContext を使用して、Amplify API を操作することができます。

ユーザーが認証されていないときはいつでも login ルートにリダイレクトしたいアプリを作ってみましょう。以下は App Router を使った例です。まず、utils フォルダーに新しい server-utils ファイルを作成します。

// utils/server-utils.ts
import { createServerRunner } from "@aws-amplify/adapter-nextjs";
import config from "../../amplifyconfiguration.json";

export const { runWithAmplifyServerContext } = createServerRunner({
  config,
});

これにより、middleware で使用する RunWithAmplifyServerContext関数 が作成されます。

// middleware.ts
import { runWithAmplifyServerContext } from "@/utils/server-utils";

// The fetchAuthSession is pulled as the server version from aws-amplify/auth/server
import { fetchAuthSession } from "aws-amplify/auth/server";
import { NextRequest, NextResponse } from "next/server";

export async function middleware(request: NextRequest) {
  const response = NextResponse.next();

  // The runWithAmplifyServerContext will run the operation below
  // in an isolated matter.
  const authenticated = await runWithAmplifyServerContext({
    nextServerContext: { request, response },
    operation: async (contextSpec) => {
      try {
        // The fetch will grab the session cookies
        const session = await fetchAuthSession(contextSpec, {});
        return session.tokens !== undefined;
      } catch (error) {
        console.log(error);
        return false;
      }
    },
  });

  // If user is authenticated then the route request will continue on
  if (authenticated) {
    return response;
  }

  // If user is not authenticated they are redirected to the /login page
  return NextResponse.redirect(new URL("/login", request.url));
}

// This config will match all routes accept /login, /api, _next/static, /_next/image
// favicon.ico
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)
     */
    "/((?!api|_next/static|_next/image|favicon.ico|login).*)",
  ],
};

新しい FetchAuthSession API を使用していることがわかります。これにより、トークンを取得してユーザーがログインしているかどうかを確認できます。ユーザーがサインインしていない場合は、login ページにリダイレクトされます。

Amplify JavaScript v6 をお試しください

Amplify community からのリクエストに応えて、このリリースを提供できることを嬉しく思います。バンドルサイズを最適化することで、Amplify はユーザーの接続環境に関係なく、すべてのユーザーに高速な読み込みを提供することを目指しています。TypeScript サポートの改善により、コーディングエクスペリエンスが向上し、エラーが減少します。Next.js インテグレーションでは、利用可能なすべての Next.js ランタイムを使用できます。

皆さんがこれらの新機能を使って何を構築するのか楽しみです!JS v6 のすべての機能を確認するには、Amplify JavaScript documentation をご覧ください。


本記事はBuilding fast Next.js apps using TypeScript and AWS Amplify JavaScript v6を翻訳したものです。翻訳はソリューションアーキテクトの髙柴元が担当致しました。