AWS Amplify のチュートリアルを参考に、Next.js + GraphQLの環境を構築してみた際のメモです。
前準備
以下のライブラリが必要。
- Node.js v12.x 以上
- npm v5.x or 以上
- git v2.14.1 以上
チュートリアルでは↑の条件だが、yarn + typescript + ts-node + typesync を入れておく
AWSアカウントがない場合は新規で作る。
Amplify CLI のインストール と configure
the Amplify CLI は global にインストールする必要がある
$ npm install -g @aws-amplify/cli
CLI をインストールできたら amplify configure
コマンドを叩いて IAMユーザを作成する
$ amplify configure
下記のように対話式で設定する
Follow these steps to set up access to your AWS account:
Sign in to your AWS administrator account:
https://console.aws.amazon.com/
Press Enter to continue
Specify the AWS Region
? region: ap-northeast-1
Specify the username of the new IAM user:
? user name: amplify-hoge
Enter the access key of the newly created user:
? accessKeyId: ********************
? secretAccessKey: ****************************************
This would update/create the AWS Profile in your local machine
? Profile Name: amplify-hoge
Successfully set up the new user.
以下、注意点
- IAM権限は最低限に絞ること(AdminstratorAccessにはしない)
↓ 具体的な手順は下記動画を参考にする
プロジェクトをセットアップ
Next.js のアプリケーションを作成
GitHub 等でレポジトリを作成しておく
Yarn を使って TypeScript でアプリケーションを作成する
$ yarn create next-app hoge-app --typescript
$ cd hoge-app
$ git init
$ git remote add origin hogehoge
$ git push -u origin main
下記コマンドを叩いてアプリケーションを起動する
yarn dev
Amplify の バックエンド環境を初期化
Amplify のバックエンド環境を初期化する
以下、注意点
? Distribution Directory Path は out
を指定すること
→ SSG のみの場合だけ上記
→ SSRやISRを使う場合は下記
- ? Distribution Directory Path は
.next
を指定すること
$ amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project hoge
The following configuration will be applied:
Project information
| Name: hoge
| 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? No
? Enter a name for the environment dev
? Choose your default editor: Visual Studio Code
? 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: out
? Distribution Directory Path: build
? Build Command: npm run-script build
? Start Command: npm run-script start
Using default provider awscloudformation
? Select the authentication method you want to use: AWS profile
For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
? Please choose the profile you want to use amplify-hoge
$ git add .
$ git commit -m "amplify init"
Amplify のライブラリをインストール
yarn add aws-amplify @aws-amplify/ui-react
$ git add .
$ git commit -m "yarn add aws-amplify"
APIとデータベースの接続
GraphQL API と データベースの作成
amplify add api
コマンドを叩くと、AppSync
を使った GraphQL API
と DynamoDB
を作成できる
$ amplify add api
? Please select from one of the below mentioned services:
# GraphQL
? Provide API name:
# nextamplified
? Choose the default authorization type for the API:
# API key
? Enter a description for the API key:
#
? After how many days from now the API key should expire (1-365):
# 7
? Do you want to configure advanced settings for the GraphQL API?
# Yes, I want to make some additional changes.
? Configure additional auth types?
# Yes
? Choose the additional authorization types you want to configure for the API:
# Amazon Cognito User Pool
? 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?
# No, I am done.
? Enable conflict detection?
# No
? Do you have an annotated GraphQL schema?
# No
? Choose a schema template:
# Single object with fields (e.g., “Todo” with ID, name, description)
? Do you want to edit the schema now?
# Yes
GraphQL スキーマを編集する
amplify/backend/api/nextamplified/schema.graphql
type Post
@model
@auth(rules: [{ allow: owner }, { allow: public, operations: [read] }]) {
id: ID!
title: String!
content: String!
}
amplify push
$ git add .
$ git commit -m "amplify add api"
Current Environment: dev
| Category | Resource name | Operation | Provider plugin |
| -------- | --------------------- | --------- | ----------------- |
| Auth | nextamplifiedXXXXXXX | Create | awscloudformation |
| Api | nextamplified | Create | awscloudformation |
? Are you sure you want to continue? Y
# You will be walked through the following questions for GraphQL code generation
? Do you want to generate code for your newly created GraphQL API? Y
? Choose the code generation language target: javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions: src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions? Y
? Enter maximum statement depth [increase from default if your schema is deeply nested]: 2
Amplify のステータスをチェック
amplify status
SSR を実装していく
↓ TypeScript で書くとこんな感じ
import { Amplify, API, Auth, withSSRContext } from "aws-amplify";
import { AmplifyAuthenticator } from "@aws-amplify/ui-react";
import { GRAPHQL_AUTH_MODE, GraphQLResult } from '@aws-amplify/api'
import type { NextPage } from 'next'
import { GetServerSideProps } from "next";
import Head from "next/head";
import awsExports from "../src/aws-exports";
import { createPost } from "../src/graphql/mutations";
import { listPosts } from "../src/graphql/queries";
import styles from "../styles/Home.module.css";
import Image from 'next/image'
import { CreatePostMutation, Post } from '../src/API'
Amplify.configure({ ...awsExports, ssr: true });
type Props = {
posts: Post[]
}
const Home = ({ posts = [] }: Props): JSX.Element => {
return (
<div className={styles.container}>
<Head>
<title>Amplify + Next.js</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Amplify + Next.js</h1>
<p className={styles.description}>
<code>{posts.length}</code>
posts
</p>
<div className={styles.grid}>
{posts.map((post) => (
<a className={styles.card} href={`/posts/${post.id}`} key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</a>
))}
<div className={styles.card}>
<h3 className={styles.title}>New Post</h3>
<AmplifyAuthenticator>
<form onSubmit={handleCreatePost}>
<fieldset>
<legend>Title</legend>
<input
defaultValue={`Today, ${new Date().toLocaleTimeString()}`}
name="title"
/>
</fieldset>
<fieldset>
<legend>Content</legend>
<textarea
defaultValue="I built an Amplify app with Next.js!"
name="content"
/>
</fieldset>
<button>Create Post</button>
<button type="button" onClick={() => Auth.signOut()}>
Sign out
</button>
</form>
</AmplifyAuthenticator>
</div>
</div>
</main>
</div>
);
}
export default Home
export const getServerSideProps: GetServerSideProps = async (context) => {
const SSR = withSSRContext({ req: context.req });
const response = await SSR.API.graphql({ query: listPosts });
return {
props: {
posts: response.data.listPosts.items,
},
};
}
export const handleCreatePost = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = new FormData(event.currentTarget);
try {
const { data } = await API.graphql({
authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS,
query: createPost,
variables: {
input: {
title: form.get("title"),
content: form.get("content"),
},
},
}) as GraphQLResult<CreatePostMutation>;
if (data && data.createPost) {
window.location.href = `/posts/${data.createPost.id}`;
}
} catch ({ errors }) {
console.error(...errors);
throw new Error(errors[0].message);
}
}
SSG を実装していく
import { Amplify, API, withSSRContext } from "aws-amplify";
import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api'
import Head from "next/head";
import { useRouter } from "next/router";
import { GetStaticProps, GetStaticPaths } from 'next';
import awsExports from "../../src/aws-exports";
import { deletePost } from "../../src/graphql/mutations";
import { getPost, listPosts } from "../../src/graphql/queries";
import styles from "../../styles/Home.module.css";
import { Post } from '../../src/API'
Amplify.configure({ ...awsExports, ssr: true });
type Props = {
post: Post
}
const PostPage = ({ post }: Props): JSX.Element => {
const router = useRouter();
if (router.isFallback) {
return (
<div className={styles.container}>
<h1 className={styles.title}>Loading…</h1>
</div>
);
}
async function handleDelete() {
try {
await API.graphql({
authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS,
query: deletePost,
variables: {
input: { id: post.id },
},
});
window.location.href = "/";
} catch ({ errors }) {
console.error(...errors);
throw new Error(errors[0].message);
}
}
return (
<div className={styles.container}>
<Head>
<title>{post.title} – Amplify + Next.js</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>{post.title}</h1>
<p className={styles.description}>{post.content}</p>
</main>
<footer className={styles.footer}>
<button onClick={handleDelete}>💥 Delete post</button>
</footer>
</div>
);
}
export default PostPage;
export const getStaticPaths: GetStaticPaths = async () => {
const SSR = withSSRContext();
const { data } = await SSR.API.graphql({ query: listPosts });
const paths = data.listPosts.items.map((post: Post) => ({
params: { id: post.id },
}));
return {
fallback: true,
paths,
};
}
export const getStaticProps: GetStaticProps = async (context) => {
const SSR = withSSRContext();
const { params } = context;
const { data } = await SSR.API.graphql({
query: getPost,
variables: {
id: params?.id,
},
});
return {
props: {
post: data.getPost,
},
};
}
$ git add .
$ git commit -m "トップと投稿ページを実装"
デプロイとホスティング
CI/CD の設定
$ amplify add hosting
? Select the plugin module to execute Hosting with Amplify Console (Managed hosting with custom domains, Continuous deployment)
? Choose a type Continuous deployment (Git-based deployments)
? Continuous deployment is configured in the Amplify Console. Please hit enter once you connect your repository
本番環境の追加
$ amplify env list
$ amplify env add
$ amplify env list
$ amplify env checkout prd
$ amplify push
Amplify で SSR や ISR をデプロイする
https://docs.aws.amazon.com/amplify/latest/userguide/server-side-rendering-amplify.html
↑ のAmplify ドキュメントに詳しく書いてある
Deploying a Next.js SSR app with Amplify
Amplify は package.json
ファイルから SSR なのか SSG なのか検知するとのこと
build script が next build
なら SSG と SSR 両方サポートされる
next build && next export
と設定してしまうと、SSGのみしかサポートされないことに注意が必要(静的ページしかなければこっちでOK)
たぶん、S3やLamnbdaとかをつかってSSR側の仕組みを作るので、SSRサポートの有無でかなりかわってくるからであろう
Amplify build settings
package.json
から レンダリング方式を検知すると、amplify.yml
のビルド設定ファイルがチェックされる
SSR app かつ amplify.yml がなければ、.next
が生成される
以下が SSR と SSG をサポートするための amplify.yml
の例
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: .next
files:
- '**/*'
cache:
paths:
- node_modules/**/*
デプロイしてもエラーになってしまう場合
- package.json で 静的エクスポートコマンドを追加する
next build
を next build & next export
にする
→ out
ディレクトリに静的ファイルが吐き出されるようになる
"scripts": {
"dev": "next dev",
"build": "next build & next export",
- amplify init したときの Distribution Directory Path を変える
build
じゃなくて 1.で吐き出されるようになった out
ディレクトリを指定する
- amplify hosting でのビルド設定を変える
Consoleにあるビルドの設定を編集して、artifacts > baseDirectory を .next
→ out
に変える
- Image Component を使わない
Error: Image Optimization using Next.js' default loader is not compatible with
next export
.
調べてみると、Amplify は SSG で Image Component は非対応とのこと
Image Component を imgタグに置き換える
振り返りメモ
Amplify のよかったところ
- GraphQL とかをカンタンに設定できる
Amplify の悪いところ
- Vercel と比べると管理画面、ビルド最適化、デプロイ方式等で劣る
- 特にSSGまわりが弱い