📣

Amplify JavaScript v6で可能になること

2023/10/30に公開

はじめに

こんにちは!
犬専用の音楽アプリ オトとりっぷでエンジニアしています、足立です!

https://www.oto-trip.com/

この記事では、Amplify JavaScript v6 で可能になることを検証していきたいと思います。

目次

  • 背景
  • 可能になること
  • セットアップ方法
  • 結論

背景

Amplify JavaScript v6 は、現在開発真っ只中の JavaScript クライアントライブラリです。
TypeScript の改善などの他に、以下の文言があります。

This preview improved Next.js SSR support for Authentication, Analytics, and Storage.

そうです。Next.js のサーバサイドの処理が改善します。
ということで、v6 の機能をちょっと覗き見してみましょう!

なお、Next.js の App Router 機能を前提に説明します。
自信がない方は、LayoutParallel Routesなどを事前にご一読いただくことをお勧めします。

可能になること

結論から言うと、ユーザー認証状況に応じて返されるページが動的に変更可能になるです。
以下の実装は、Layout と Parallel Routes を活用した例です。

src/app/layout.tsx
src/app/layout.tsx
import { fetchAuthSession } from 'aws-amplify/auth/server';
import { cookies } from 'next/headers';
import { Suspense } from 'react';

import { ConfigureAmplifyClientSide } from '@/app/_component/ConfigureAmplifyClientSide';
import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils';

export const dynamic = 'force-dynamic';

const RootLayout = async ({
  children,
  auth,
  unauth,
}: {
  children: React.ReactNode;
  auth: React.ReactNode;
  unauth: React.ReactNode;
}) => {
  const authenticated = await runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: async (contextSpec) => {
      try {
        const session = await fetchAuthSession(contextSpec, {});
        return session.tokens !== undefined;
      } catch {
        return false;
      }
    },
  });

  return (
    <html lang='en'>
      <head>
        <link rel='icon' href='/favicon.png' />
      </head>
      <body>
        {children}

        <Suspense fallback={<div>loading...</div>}>
          {authenticated ? auth : unauth}
        </Suspense>

      </body>
      <ConfigureAmplifyClientSide />
    </html>
  );
};
export default RootLayout;

同じ Path へのアクセスなのに表示される内容を分けられるようになります。
これらをサーバサイドで出し分けているわけです。

今までは、クライアントサイドで出し分けることは確かに可能でした。しかしその場合、センシティブな内容が含まれている場合にそれも含まれて返却される可能性がありました。
そこで上記の実装のようにサーバサイドで出し分けられるようになることで、アプリケーションの安全性が高まります。

セットアップ方法

Init Project

まずは、Next.js の新規プロジェクトを作成します。

$ npx create-next-app next-amplified
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … No
✔ Would you like to use Tailwind CSS? … No
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias? … Yes
✔ What import alias would you like configured? … @/*

次に不要な記述を削除します。

src/app/page.tsx
const Home = () => {
  return (
    <main>
      <h1>Hello Amplify!</h1>
    </main>
  );
}
export default Home
src/app/layout.tsx
const RootLayout = ({ children }: { children: React.ReactNode }) => {
  return (
    <html lang='en'>
      <body>{children}</body>
    </html>
  );
};
export default RootLayout;

これでシンプルなページが作成できました。

Init Amplify

次に Amplify init してバックエンドリソースを作成します。

$ amplify init
? Enter a name for the project (nextamplified)
The following configuration will be applied:

Project information
| Name: nextamplified
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: react
| Source Directory Path: src
| Distribution Directory Path: build
| Build Command: npm run-script build
| Start Command: npm run-script start

? Initialize the project with the above configuration? Yes
? Select the authentication method you want to use: AWS profile

Auth を追加して、認証機能を使用できるようにします。
今回は検証環境構築なので、一番簡単なセッティングにします。

$ amplify add auth
Do you want to use the default authentication and security configuration?
> Default configuration
How do you want users to be able to sign in?
> Username
Do you want to configure advanced settings? (Use arrow keys)
❯ No, I am done.

$ amplify push

これで、バックエンドのリソースが作成できました。

Next.js 側のセッティング

まず、必要なライブラリを追加します。

$ npm install aws-amplifyt @aws-amplify/adapter-nextjs

次に、next.config.jsを書き換えて、サーバサイドで Amplify ライブラリが使用できるようにします。 ← 不要になりました!

まず、src/utilsdir 以下に amplifyServerUtils.tsというファイルを作成します。

src/utils/amplifyServerUtils.ts
import config from '@/amplifyconfiguration.json';
import { createServerRunner } from '@aws-amplify/adapter-nextjs';

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

これで、サーバサイドで Amplify が使う事が可能になります。

次に、src/app dir 以下に_component/ConfigureAmplifyClientSide.tsと言うファイルを作成し、そのファイルを layout で読み込みます。
Next.js では頭に_が付いたディレクトリはルーティングの対象外になります。詳しくは公式をご覧ください。

src/_component/ConfigureAmplifyClientSide.ts
'use client';

import { Amplify } from 'aws-amplify';

import config from '@/aws-exports';

Amplify.configure(config, { ssr: true });

export const ConfigureAmplifyClientSide = () => {
  return null;
};
src/app/layout.tsx
+ import { ConfigureAmplifyClientSide } from '@/app/_component/ConfigureAmplifyClientSide';

const RootLayout = ({ children }: { children: React.ReactNode }) => {
  return (
    <html lang='en'>
      <body>{children}</body>
+      <ConfigureAmplifyClientSide />
    </html>
  );
};
export default RootLayout;

これでクライアントサイドでも Amplify を使うことが可能になります。

ページ作成

次に Next.js のセッティングをしていきます。
まず以下の通り、Parallel Routes 用に頭に@がついたディレクトリに page.tsx ファイルを作っていきます。

src
 ├── app
      ├── _component
      |    └── ConfigureAmplifyClientSide.tsx
      ├── @auth
      |    └── page.tsx
      ├── @unauth
      |    └── page.tsx
      ├── layout.tsx
      ├── page.tsx

 |
 ├── utils
      ├── amplifyServerUtils.ts

src/app/@auth/page.tsx
'use client';

import { signOut } from 'aws-amplify/auth';
import { useRouter } from 'next/navigation';

const Page = () => {
  const router = useRouter();

  const handleSignOut = async () => {
    try {
      await signOut();
      router.refresh();
    } catch (error) {
      console.log('error signing out: ', error);
    }
  };

  return (
    <div>
      <p>auth</p>

      <button onClick={handleSignOut}>ログアウト</button>
    </div>
  );
};

export default Page;
src/app/@unauth/page.tsx
'use client';

import { signIn } from 'aws-amplify/auth';
import { useRouter } from 'next/navigation';
import { useState } from 'react';

const Page = () => {
  const router = useRouter();

  const [username, setUserName] = useState<string>('');
  const [password, setPassword] = useState<string>('');

  const handleSignIn = async () => {
    try {
      await signIn({ username, password });
      router.refresh();
    } catch (error) {
      console.log('error signing in', error);
    }
  };

  return (
    <div>
      <p>un auth</p>

      <input
        type='text'
        placeholder='username'
        onChange={(e) => setUserName(e.target.value)}
      />
      <input
        type='password'
        placeholder='password'
        onChange={(e) => setPassword(e.target.value)}
      />

      <button onClick={handleSignIn}>ログイン</button>
    </div>
  );
};

export default Page;

次にlayout.tsxを以下の通り修正します。

src/app/layout.tsx
import { fetchAuthSession } from 'aws-amplify/auth/server';
import { cookies } from 'next/headers';
import { Suspense } from 'react';

import { ConfigureAmplifyClientSide } from '@/app/_component/ConfigureAmplifyClientSide';
import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils';

export const dynamic = 'force-dynamic';

const RootLayout = async ({
  children,
  auth,
  unauth,
}: {
  children: React.ReactNode;
  auth: React.ReactNode;
  unauth: React.ReactNode;
}) => {
  const authenticated = await runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: async (contextSpec) => {
      try {
        const session = await fetchAuthSession(contextSpec, {});
        return session.tokens !== undefined;
      } catch {
        return false;
      }
    },
  });

  return (
    <html lang='en'>
      <head>
        <link rel='icon' href='/favicon.png' />
      </head>
      <body>
        {children}

        <Suspense fallback={<div>loading...</div>}>
          {authenticated ? auth : unauth}
        </Suspense>

      </body>
      <ConfigureAmplifyClientSide />
    </html>
  );
};
export default RootLayout;

ポイントは、runWithAmplifyServerContextです。
この関数のおかげでauthenticated判定がサーバサイドで実施されます。

AWS Amplify Hosting

デプロイ方法は v5 の場合と変わりません。
AWS Amplify のマネージメントコンソールから Git リポジトリ連携し、Next.js セッティングでデプロイするだけです。
詳細な手順は、AWS 公式ブログをご参照ください。

結論

最終的にこうなります!(再掲)

画像はローカルサーバにアクセスしていますが、ホスティング先にアクセスした場合も同様です。
何度も言いますが、出し分けをサーバサイドで行っています。なので、ログもサーバサイドに出力されます。

最後に

ここまで読んでいただきありがとうございました。
Amplify の開発ロードマップが Next.js へ追従してくれているのは本当にありがたいです。
ちなみに、Amplify 公式の情報は以下にありますので、気になる方はチェックしてみてください。

https://docs.amplify.aws/lib-v1/ssr/nextjs/q/platform/js/

これからも Next.js の App Router 関連技術はキャッチアップしていきたいです。
もし犬専用の音楽アプリに興味を持っていただけたら、ぜひダウンロードしてみてください!

https://www.oto-trip.com/

Discussion