サイトアイコン Opvelのブログ

[Amplify + Next.js] 簡単な掲示板を作成

前にReactで作った掲示板をNextにしてみる。SSRをまず使ってみる。

nextでプロジェクトを作成

npx create-next-app

amplify init

? Enter a name for the project nextamplify1
The following configuration will be applied:

Project information
| Name: nextamplify1
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: react
| Source Directory Path: src
| Distribution Directory Path: build
| Build Command: npm.cmd run-script build
| Start Command: npm.cmd 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:  src
? Distribution Directory Path: .next                        <<===== ここ
? Build Command:  npm.cmd run-script build
? Start Command: npm.cmd run-script start
Using default provider  awscloudformation
? Select the authentication method you want to use: AWS profile

今のcliのバージョンなら認識するかもしれないが、自分のときは Distribution Directory Path がbuildになったので、.nextに変更する。上記の部分。

Githubとの連帯とデプロイ

githubにレポジトリを作って、ブラウザでamplify consoleにアクセスして、Githubと連帯する。

このとき、設定を追加する項目で、nextのバージョン : latestを追加する。

参考 : AWS AmplifyでNext.jsのISRを試してみる

https://dev.classmethod.jp/articles/amplify-next-js-isr/

ここまででnextをAmplifyでデプロイすることができた。

Amplify APIを追加

amplify add apiでAPIを追加。

amplify add api

amplify push

amplify push

ライブラリをインストール

npm install aws-amplify @aws-amplify/ui-react

_app.jsに以下を追加

import Amplify from 'aws-amplify';
import config from '../src/aws-exports';
Amplify.configure({
  ...config, ssr: true
});

GraphQLスキーマを変更

GraphQLスキーマを変更してamplify pushする。前の記事参照

Nextコードを書いていく

indexページを移植

import { useState } from "react";
import { API, graphqlOperation, withSSRContext } from "aws-amplify";
import { byCreatedAt } from "../src/graphql/queries.js";
import { createComment, createThread } from "../src/graphql/mutations.js";
import Link from "next/link";

// SSR時にスレッド一覧を取得
export async function getServerSideProps(req) {
  const { API } = withSSRContext(req);
  const { data } = await API.graphql(
    graphqlOperation(byCreatedAt, {
      type: "t",
      sortDirection: "DESC",
    })
  );
  console.log(data);

  return {
    props: {
      threadList: data.byCreatedAt.items,
    },
  };
}

function Index(props) {
  const [threadList, setThreadList] = useState(props.threadList);
  const formFirstValue = { title: "", firstComment: "" };
  const [formState, setFormState] = useState(formFirstValue);

  // フォームをクリック時の動作
  const onSubmitForm = async (e) => {
    e.preventDefault();
    // フォームが空なら終了
    if (!formState.title || !formState.firstComment) return;
    // スレッドとコメントの作成
    const newThread = await createNewThread();
    await createFirstComment(newThread.id);
    setFormState(formFirstValue);
    // 画面に反映
    setThreadList([newThread, ...threadList]);
  };

  // スレッドの作成
  const createNewThread = async () => {
    const newThread = { title: formState.title, type: "t" };
    const { data } = await API.graphql(
      graphqlOperation(createThread, { input: newThread })
    );
    console.log(data);
    return data.createThread;
  };

  // 最初のコメントを作成
  const createFirstComment = async (threadId) => {
    const newComment = { threadId: threadId, title: formState.firstComment };
    const { data } = await API.graphql(
      graphqlOperation(createComment, { input: newComment })
    );
    console.log(data);
  };

  // フォームに入力時、formStateを更新する
  const onChange = (event, propaty) => {
    setFormState({ ...formState, [propaty]: event.target.value });
  };

  return (
    <div>
      <div>掲示板(Amplify + Next)</div>
      <div>
        {threadList.map((thread, index) => {
          return (
            <div key={index}>
              <Link href={"/" + thread.id}>
                <a>{thread.title}</a>
              </Link>
            </div>
          );
        })}
      </div>
      <div>
        <form>
          <div>
            <div>
              <label htmlFor="threadTitle">スレッドタイトル</label>
            </div>
            <input
              id="threadTitle"
              value={formState.title}
              onChange={(event) => onChange(event, "title")}
            ></input>
          </div>
          <div>
            <div>
              <label htmlFor="threadFirstComment">最初のコメント</label>
            </div>
            <input
              id="threadFirstComment"
              value={formState.firstComment}
              onChange={(event) => onChange(event, "firstComment")}
            ></input>
          </div>
          <button onClick={onSubmitForm}>送信</button>
        </form>
      </div>
    </div>
  );
}

export default Index;

threadページ

import { API, graphqlOperation, withSSRContext } from "aws-amplify";
import { useEffect, useState, useRef } from "react";
import { createComment } from "../src/graphql/mutations";
import { getThread } from "../src/graphql/queries";
import { onCommentByThreadId } from "../src/graphql/subscriptions";
import { useRouter } from "next/router";

// SSRでスレッド情報を取得
export async function getServerSideProps(req) {
  const { API } = withSSRContext(req);
  const { data } = await API.graphql(
    graphqlOperation(getThread, { id: req.params.threadId })
  );
  console.log(data);
  return {
    props: {
      thread: data.getThread,
    },
  };
}

export default function Thread(props) {
  const router = useRouter();
  const { threadId } = router.query;
  const [thread, setThread] = useState(props.thread);
  const [commentForm, setCommentForm] = useState({ title: "" });
  const subscriptionRef = useRef();

  // サブスクを設定する
  useEffect(() => {
    attachSubscription();
    return () => {
      if (subscriptionRef) subscriptionRef.current.unsubscribe();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // subscriptionを設定
  const attachSubscription = () => {
    subscriptionRef.current = API.graphql(
      graphqlOperation(onCommentByThreadId, { threadId: threadId })
    ).subscribe({
      next: (data) => {
        console.log(data);
        addComment(data.value.data.onCommentByThreadId);
      },
      error: (error) => console.warn(error),
    });
  };

  // コメントのリストに新たに追加
  const addComment = (newComment) => {
    setThread((thread) => {
      // リスト内に既にあったら追加しない
      const idx = thread.comments.items.findIndex(
        (comment) => comment.id === newComment.id
      );
      if (idx !== -1) return thread;
      console.log("update comment list", thread);
      return {
        ...thread,
        comments: { items: [...thread.comments.items, newComment] },
      };
    });
  };

  // フォーム送信時
  const onSubmit = async (e) => {
    e.preventDefault();
    if (!commentForm.title) return;
    const newComment = await createNewComment();
    setCommentForm({ title: "" });
    // thread情報の更新
    setThread((thread) => {
      return {
        ...thread,
        comments: { items: [...thread.comments.items, newComment] },
      };
    });
  };

  // コメントの作成
  const createNewComment = async () => {
    const { data } = await API.graphql(
      graphqlOperation(createComment, {
        input: { threadId: threadId, title: commentForm.title },
      })
    );
    console.log(data);
    return data.createComment;
  };

  return (
    <div>
      <h1>{thread.title}</h1>
      {thread.comments.items.map((comment, idx) => {
        return <div key={idx}>{comment.title}</div>;
      })}
      <div>
        <form>
          <div>
            <label htmlFor="commentForm">コメント</label>
          </div>
          <input
            id="commentForm"
            value={commentForm.title}
            onChange={(event) => setCommentForm({ title: event.target.value })}
          ></input>
          <button onClick={onSubmit}>送信</button>
        </form>
      </div>
    </div>
  );
}

動いた。ヤッター。

サイト公開アドレス :

https://main.d1xvmlla5wn67x.amplifyapp.com/

公式ドキュメント

AWS AmplifyJavaScriptライブラリのSSRサポート

https://aws.amazon.com/jp/blogs/mobile/ssr-support-for-aws-amplify-javascript-libraries/

AWSAmplifyでリアルタイムデータを使用してNext.jsSSRアプリをホストします

https://aws.amazon.com/jp/blogs/mobile/host-a-next-js-ssr-app-with-real-time-data-on-aws-amplify/

HOSTING Next.js

https://docs.amplify.aws/guides/hosting/nextjs/q/platform/js#adding-amplify-hosting-1

モバイルバージョンを終了