Amplify ConsoleでServer Side Renderingするチュートリアルを作った

9 min read読了の目安(約8800字

はじめに

おはようございます、加藤です。先日のアップデートでAmplify ConsoleでNext.jsのv9でSSRが出来るようになりました。普段触らないAmplify CLI に慣れることも兼ねてAmplifyフレームワークでNext.jsをSSRするチュートリアルを作ってみました。

前提

  • IAMポリシーAdministratorAccessがアタッチされたIAMユーザーが端末に登録されている(~/.aws/configure に書かれている)
  • Node.js v16とNPM v7がインストールされている
  • Amplifyフレームワークに関して基礎知識がある(CLIとConsoleに関して)

開発環境のセットアップとリポジトリの初期生成

Amplify CLIのインストール

npm install -g amplify

リポジトリのセットアップ

GitHubにプライベートでリポジトリを作成します。私はamplify-ssr-tutorialという名前で作成しました。

git pull ${REPO_URL}
npx create-next-app --example with-typescript ${REPO_DIR}
cd ${REPO_DIR}
amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project amplifyssrtutorial
? Enter a name for the environment dev
? Choose your default editor: IntelliJ IDEA
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using react
? Source Directory Path:  src
? Distribution Directory Path: build
? Build Command:  npm run-script build
? Start Command: npm run-script start
Using default provider  awscloudformation

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html

? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use amplify
Adding backend environment dev to AWS Amplify Console app: xxxxxxxxxxxxx
⠧ Initializing project in the cloud...

CREATE_IN_PROGRESS ...

✔ Successfully created initial AWS cloud resources for deployments.
✔ Initialized provider successfully.
Initialized your environment successfully.

Your project has been successfully initialized and connected to the cloud!

Some next steps:
"amplify status" will show you what you've added already and if it's locally configured or deployed
"amplify add <category>" will allow you to add features like user login or a backend API
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify console" to open the Amplify Console and view your project status
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

Pro tip:
Try "amplify add api" to create a backend API and then "amplify publish" to deploy everything
git add .
git commit -m "🎉 initial commit"
git push

amplify/team-provider-info.jsonなどの固有の環境のパラメータを含むファイルもGit リポジトリにプッシュします。

静的WEBサイトホスティング環境の構築

amplify add hosting
Scanning for plugins...
Plugin scan successful
? Select the plugin module to execute Hosting with Amplify Console (Managed hosting with custom domains, Continuous deploy
ment)
? Choose a type Continuous deployment (Git-based deployments)
? Continuous deployment is configured in the Amplify Console. Please hit enter once you connect your repository

ブラウザが開くので GitHub を選択して次へ進みます。

リポジトリとブランチを選択して次へ進みます。

バックエンド環境に dev を選択して次へ進みます。

パラメータを確認して次へ進みます。

検証までグリーンになるのをまってから、ドメインをクリックします。

WEBサイトが表示されればOKです。

コンソールを再度開き、Enterを押下すると処理が再開してデプロイされたURLが表示されます。

Amplify hosting urls:
┌──────────────┬──────────────────────────────────────────────┐
│ FrontEnd Env │ Domain                                       │
├──────────────┼──────────────────────────────────────────────┤
│ master       │ https://master.xxxxxxxxxxxxxx.amplifyapp.com │
└──────────────┴──────────────────────────────────────────────┘

しかし、この状態でUsers APIを開くとエラーが発生してしまいます。

CloudWatch Logsを見てみるとMODULE_NOT_FOUNDのエラーが発生していました。

Amplify ConsoleによるSSRはNext.js v9をサポートしているのにNext.js v10を使ってしまっている事が原因な気がしたので、バージョンをダウングレードしてリポジトリにPushします。
※Amplify ConsoleによるSSRはLambda@Edgeを使うためCloudWatch Logsの出力先リージョンは実行されたリージョンになります、日本からのアクセスなら東京リージョンになります。もし東京リージョンに出力されない場合は大阪リージョンを確認してください。

Next.jsをv9.5.5へダウングレードします、合わせてReactのバージョンも下げます。

package.json

{
  "name": "with-typescript",
  "version": "1.0.0",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "type-check": "tsc"
  },
  "dependencies": {
    "next": "9.5.5",
    "react": "16.12.0",
    "react-dom": "16.12.0"
  },
  "devDependencies": {
    "@types/node": "^12.12.21",
    "@types/react": "16.9.56",
    "@types/react-dom": "16.9.13",
    "typescript": "4.0"
  },
  "license": "MIT"
}

定義をダウングレードしたら、package-lock.jsonを更新する為にnpm installを実行します。その後、リポジトリにCommitしてPushします。

git add .
git commit -m "⬇️ downgrade nextjs to v9 for amplify console ssr"
git push

デプロイ完了後にUsers APIのページを開くと[{"id":101,"name":"Alice"},{"id":102,"name":"Bob"},{"id":103,"name":"Caroline"},{"id":104,"name":"Dave"}]とJSONが表示されればOKです。

Server Side Renderingする

getServerSidePropsを使って名言を表示するページをSSRで作ります。名言の取得には https://meigen.doodlenote.net/ を使わせて頂きました。

下記の2つのファイルを作成・編集します。

// pages/meigen.tsx
import { GetServerSideProps } from "next";

interface IMeigen {
  meigen: string;
  auther: string;
}

const isMeigen = (arg: any): arg is IMeigen => {
  if (typeof arg.meigen !== "string") {
    return false;
  }
  if (typeof arg.auther !== "string") {
    return false;
  }

  return true;
};

const Meigen = ({ data }: { data: IMeigen }) => {
  return (
    <>
      著者: {data.auther}
      <br />
      名言: {data.meigen}
    </>
  );
};

// サーバーサイドで処理させる為に`getServerSideProps`を使用する
export const getServerSideProps: GetServerSideProps = async () => {
  const res = await fetch(`https://meigen.doodlenote.net/api/json.php`);
  const data = (await res.json())[0];

  if (!isMeigen(data)) {
    return { props: { meigen: "failed", auther: "failed" } };
  }

  return { props: { data } };
};

export default Meigen;
// pages/index.tsx
import Link from "next/link";
import Layout from "../components/Layout";

const IndexPage = () => (
  <Layout title="Home | Next.js + TypeScript Example">
    <h1>Hello Next.js 👋</h1>
    <p>
      <Link href="/about">
        <a>About</a>
      </Link>{" "}
      <Link href="/meigen">
        <a>名言</a>
      </Link>
    </p>
  </Layout>
);

export default IndexPage;

作成・編集が終わったらCommitしてPushします。

git add .
git commit -m "✨ add meigen"
git push

デプロイが完了したらトップページからリンクをたどって/meigen/へアクセスして名言が表示されるかを確認します。この時に開発者ツールを開き名言APIにクライアントが直接通信していないこと(=SSRされていること)を確認します。

Amplify ConsoleはどうやってSSRを実現しているか

Amplify ConsoleはLambda@Edgeを使うことでNext.jsでのSSRを実現しているようです。初回デプロイ時に、CloudFrontおよびLambda@Edgeが作成され、更新されるたびにデプロイが走っていました。Amplify Consoleはサービスのバックエンドで使用するAWSリソースを見せない(恐らくCloudFrontやS3がユーザーには見せない)仕様で、私はこれを気に入っていたのでこれらのリソースが見えてしまうのは少し残念でした。(AWS WAFを設定できるなどのメリットはあるけど)

Behaviorsの設定やLambda関数の説明文が完全に同じなので、Serverless Next.jsを裏で使っていると推測しています。

Serverless Next.jsとの比較にはこちらのブログを参考にさせてもらいました。Serverless Nextjs Pluginを試してみた | DevelopersIO

Amplify Console SSRがNext.js v9しかサポートしていないのは、Serverless Next.jsがv9しかサポートしていないからかなーと思っています。

あとがき

Amplify ConsoleのSSR機能を試しながらチュートリアルとしてまとめてみました。本当は認証ありでSSRをあったほうが良いんでしょうが記事が長くなった&SSRで認証ありはNext.js的に推奨していないっぽいので省略しました。使いたい方はAWSの公式ブログやAmplify SDKのドキュメントが参考になると思います。
Lambda@Edgeを使うと(バージニア北部リージョンをメインで使っている場合以外は)デプロイ先リージョンが複数になりデプロイフローが複雑化しがちという問題がありました。バージニア北部へのデプロイはAmplify Consoleが代わりに行ってくれるので助かります。
ただし、使用できるNext.jsのバージョンが縛られるのでプロダクション環境で利用するかは十分に検討した方が良いと思います。v10への対応やCloudFrontおよびLambdaの隠蔽を今後のバージョンアップに期待します。

参考