2020年5月7日

Firebase Hostingにデプロイした静的サイトにFirebase Analyticsを追加する(Next.js)

まずは、Firebaseの管理画面から、該当のプロジェクトでAnalyticsを有効化します。


Google アナリティクスを使ってみる






ここではNext.jsからエクスポートした静的サイトを例にします。

pages/index.js など、全てのページで共通に含まれる部分で、下のようにHeadコンポーネントを使ってscriptタグを埋め込みます。

要は下の赤字部分の3行のscriptタグがHTMLのhead部分に含まれるようにすれば良いということですね。

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
        <script src="/__/firebase/7.14.2/firebase-app.js"></script>
        <script src="/__/firebase/7.14.2/firebase-analytics.js"></script>
        <script src="/__/firebase/init.js"></script>
      </Head>
      <section className={utilStyles.headingMd}>
        <p>[Your Self Introduction]</p>
      </section>
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              <Link href="/posts/[id]" as={`/posts/${id}`}>
                <a>{title}</a>
              </Link>
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  );
}


次に、下のドキュメントの通りにイベントを送信する必要があります。

イベントをロギングする

このために、「send-ga-event.js」 と 「use-send-screenview.js」という2つのファイルを作成しました。


send-ga-event.js

export function sendGAEvent(eventName, params) {
  const firebase = window && window.firebase;
  if (!firebase) return;
  try {
    firebase.analytics().logEvent(eventName, params);
  } catch (error) {
    console.error(error);
  }
}


use-send-screenview.js

import { useEffect } from 'react';
import { sendGAEvent } from './send-ga-event';
export function useSendScreenView(screenName) {
  return useEffect(() => {
    const params = { screen_name: screenName };
    sendGAEvent('screen_view', params);
  }, []);
}

上で定義したuseSendScreenView()という名前のフックを pages/index.jsから呼び出すことでAnalyticsにイベントを送信します。


pages/index.js

import { useSendScreenView } from '../utils/use-send-screenview';
export default function Home({ allPostsData }) {
  useSendScreenView('home');
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
        {process.env.NODE_ENV === 'production' && (
          <>
            <script src="/__/firebase/7.14.2/firebase-app.js"></script>
            <script src="/__/firebase/7.14.2/firebase-analytics.js"></script>
            <script src="/__/firebase/init.js"></script>
          </>
        )}
      </Head>
      <section className={utilStyles.headingMd}>
        <p>[Your Self Introduction]</p>

(以下省略...)


これらの変更を行ったあと、

npm run build && npm run export
firebase deploy 

でデプロイしてサイトを更新します。

更新後のページを開くと、数秒でAnalyticsの画面にイベントが表示されるのが確認出来ます。








  

Next.jsで静的サイトを出力してFirebase Hostingでホスティングする

まずは公式のドキュメントにしたがってNext.jsのアプリケーションを作成しましょう。

Create a Next.js App | Learn Next.js 


package.jsonファイルのscriptsセクションに下記を追加しておきます。

  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "export": "next export"
  },


npm run build && npm run export を実行して outフォルダに結果が出力されるのを確認します。



次にFirebase CLIを初期化します。


firebase login

firebase init



カーソルキーで「Hosting」を選択してスペースキーを押し、Enterで確定です。

Firebaseの管理画面でまだ「プロジェクト」を作成していない場合は、プロジェクトIDと名称を入力して新規作成します。既に使われているIDを入れてしまうとエラーになるので、ユニークなIDを入力しましょう。

アップロード対象のフォルダを聞かれるので、デフォルトの「public」ではなく「out」を指定します。


firebase deploy を実行して、「Deploy complete!」と表示されれば完了です!


慣れれば3分もかからずに出来てしまいますね。


バックエンド処理にCloud Functionsを使ったりするような場合も、下のドキュメントの通り設定すれば決して難しくはありません。

本当に便利な世の中になったものです。^^)


あ、特にFirebase Hostingでなくても良いのであれば、もちろんNext.jsの開発元であるVercel(旧Zeit)社のサービスを使うのが一番簡単でオススメです。



こちらであれば、静的サイトでなくSSRありのサイトであっても、またバックエンドのAPIが必要な場合でも、何も考えずにデプロイしてしまえばあとはサービス側で上手くやってくれるみたいなので、こちらも素晴らしいと思います。







 

2020年5月4日

ReactでSVGファイルをコンポーネントとしてレンダリングする

egghead.ioのレッスンを観ていて良さそうなものがあったので、メモしておきます。


Add SVGs as React Components with Create React App 2.0
https://egghead.io/lessons/react-add-svgs-as-react-components-with-create-react-app-2-0

Create React App のドキュメントにも記述があります。
https://create-react-app.dev/docs/adding-images-fonts-and-files


Create React Appで作ったReactアプリケーションでは画像ファイルを下のようにインポートしてコンポーネント内で参照することが出来ます。

import logo from './logo.png';
function Header() {
  return <img alt="Logo" src="{logo}" />;
}

もちろん画像の形式がSVGであっても同様です。

import logo from './logo.svg';
function Header() {
  return <img alt="Logo" src="{logo}" />;
}

ただ、SVG形式の場合はインポートの仕方を下のように変えるとReactコンポーネントとしても使えるようになるそうです。

import { ReactComponent as Logo } from './logo.svg';
function App() {
  return (
    <div className="App">
<header className="App-header">
<Logo className="App-logo" />
</header>
</div>);
}


このようにすると、HTMLとしてレンダリングされる際のタグが imgタグではなく、svgタグになります。

svgタグになってうれしい点は、SVG内の個々の要素(図形)をCSSでコントロール出来るようになることです。


例えば、線の色や太さを変えたり、アニメーションを付け加えたりすることが可能になります。


下の例では、Reactロゴのpathエレメントの部分のみ点線にして、さらに点線のオフセットをアニメーションで動かしています。

.App-logo path {
  stroke: palegoldenrod;
  stroke-width: 10px;
  fill: none;
  stroke-dasharray: 35px 15px;
  animation: orbit 1s infinite linear;
} @keyframes orbit {
  to {
    stroke-dashoffset: 50px;
  }
}

実行するとこんな感じになります。









2020年4月20日

AWS AmplifyとDynamoDBでサーバーレスなREST APIを構築する

前回はユーザー認証機能を付けましたが、まだデータの永続化が出来ていないのでTodoアプリとしては未完成です。



  1) Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする

  2) AWS AmplifyでReactアプリにユーザー認証機能を追加する

  3) AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する




今回はサーバーレスなREST APIを追加してデータをデータベースに保存出来るようにします。

amplify status で現状を確認すると、下のようになっています。



下のコマンドでAPIカテゴリを追加します。

amplify add api 

まず GraphQL か REST かを選ぶように言われます。

Web上で見つかる情報にはGraphQLを使ったものが圧倒的に多いように思いますが、ここではあえてRESTを選びます。



DynamoDB上に作成するテーブル名まで入力すると、次に作成するカラム情報の入力になります。

ドキュメントをざっと読んだところ、DynamoDBでは基本的に1アプリケーションで使うテーブルは出来るだけ少ない方が望ましいらしく、「1テーブルで済ませられればそれが最も良い」との事です。



長年リレーショナルデータベースに馴染んだ身としてはなかなか目から鱗な考え方です。

どうやらDynamoDBでのスキーマ設計の勘所は、「パーティションキー」と「ソートキー」および「グローバル・セカンダリインデックス」「ローカル・セカンダリインデックス」を上手く使うことにあるようです。

DynamoDBのテーブル設計をするとき、自分に問いかけていること – 或る阿呆の記 ( https://hack-le.com/dynamodb-query/ )

この辺り、非常に奥が深そうで面白いのですがとりあえず今はシンプルなTodoリストアプリを作りたいだけなので、極力簡単な方法で行きたいと思います。



パーティションキーを「pk」、ソートキーを「sk」とし、3つ目のカラムはmap型で「data」としておきました。map型にはJSONを格納できるので、こうしておけば後からの仕様変更にもある程度柔軟に対応することが可能になるかと思います。

パーティションキーとソートキーを何にするかというのは、アプリケーションの要件によって大きく変わる部分で、開発効率にも大きく関わってきます。


今回は、pkカラムには認証されたユーザーのidに "user:" というプリフィックスを付加して格納し、skカラムにはTodoアイテムのid(ランダムに生成されたもの)の先頭に "todo:"というプリフィックスを付加して格納することにしました。

こうすることでユーザーのidが分かればそのユーザーに属するタスクの一覧を簡単に取得することが出来るようになります。

実際のクエリーとしては、
- pkが "user:" + ユーザーid に一致する
- skが "todo:" で始まる
という条件で検索することになります。

DynamoDBではパーティションキーは完全一致でしか検索出来ませんが、ソートキーは部分一致や範囲検索が可能なので、このようなクエリーが可能になります。

また、ユーザーに属するデータでTodo項目以外のデータを保存したい場合には、skに付けるプリフィックスを "todo:" 以外のものにすれば対応出来ます。

例えば、ユーザーごとのアプリ設定を保存したい場合は、pkは同じでskを "pref:" として保存すれば、簡単にそのレコードを一意に指定して読み出す事が出来ます。

1アプリケーションで一つのテーブルしか使わない、というのはこのように2つのキー(およびセカンダリインデックス)を上手く使って複数の種類のデータを保存する、ということになるのかなと思います。


カラム設定の入力が終わると、テーブルへのアクセス権の設定をするか聞かれるので、認証していないユーザーはAPIへのアクセスが出来ない様に設定しておきます。




次に amplify push を実行して、追加した設定をAWS側に反映します。


完了するとAWS側では下記のリソースが作成されています。

- API Gateway
- Lambda
- DynamoDB


これらのうち、Lambda関数は自動生成されたコードだと上手く動かず、何をしているのかを理解した上でアプリケーションの要件に合わせてそこそこ手を加える必要がありました。


/amplify/backend/function/(Function名)/src/app.js (一部のみ抜粋)
-----

-----


アプリケーション側で扱うModelとしては

    {      
        id: 'todo:12312-312132',      
        text: 'ミルクを買う',      
        done: false 
    }

のような形になっているのですが、DynamoDBに保存されるのは、

    {
        pk: 'user:aaaaaaaaaaa',
        sk: 'todo:12312-312132',
        data: {
            text: 'ミルクを買う',
            done: false
        }
    }

という形になっています。

このため app.js の内部でAPI Gatewayから受け取ったオブジェクトをDynamoDBのスキーマに合った形式に適宜変換してから保存しています。



さて、次はReactアプリの方を変更して行きます。


この辺りのドキュメントを見ながらAPIへのアクセス処理を実装します。

Amplify Docs –– Fetching Data 



基本的には、

import { API } from 'aws-amplify';

でAPIクラスをインポートした後、

await API.post(apiName, path, options);  // CREATE
await API.get(apiName, path, options);  // READ
await API.put(apiName, path, options);  // UPDATE
await API.del(apiName, path, options);  // DELETE

でLamdaで作成してあるAPIにアクセスすることが出来るので、各アクションからこれらを適宜呼び出しています。


React側の実装としては、Hooks(useEffect, useReducer)を使って作っています。


/services/todo-service.js
-----

-----



ここまででなんとかシンプルなTodoリストアプリが完成しました。



  • Amplify Consoleによるスタティックなサイトのホスティングは簡単・超便利!

  • Amplify CLIでのユーザー認証機能の追加はパラメータの設定が最初ちょっと大変だけど、何回か試行錯誤して分かってしまえばこちらも簡単で素晴らしい!

  • Amplify CLIでのREST APIの作成は、DynamoDBの特性を理解して設定する事が超重要!(*



* もちろんDynamoDB以外の任意のデータベースを使うことも可能。


現在のソースコードは下記から確認可能です!

https://github.com/mikehibm/amplify-test01




  1) Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする

  2) AWS AmplifyでReactアプリにユーザー認証機能を追加する

  3) AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する






 

2020年4月17日

AWS AmplifyでReactアプリにユーザー認証機能を追加する

前回はNext.jsでエクスポートしたWebアプリをAmplify Consoleでホスティングするところまで行いました。

今回はこのアプリにAWS Cognitoによるユーザー認証を追加したいと思います。



まず amplify status で現在の状態を確認しておきます。



次に、 amplify auth add を実行。




最初に「Do you want to use the default authentication and security configuration?」と聞かれます。

ここでデフォルト設定を使って試したところ、サインアップの際にメールアドレスの他に電話番号の入力も必須になってしまったので、それを回避するために今度は「Manual configuration」を選択して次に進みました。


次に、amplify push を実行して追加した設定をAWS側に反映します。


コマンドが完了すると、AWS側では必要なリソース(Cognito User Pool、Lambdaファンクションなど)が作成されているのが分かります。


次にReactアプリケーションの方でCognitoに接続するために、必要なライブラリを追加します。

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


ログイン画面の表示には「Amplify UI Components」を使います。

/pagesフォルダ内にlogin.jsを作成し下のコードを入力しました。

----------

----------

これだけで、http://localhost:3000/login を開くと下のようなログイン画面が表示されるようになります。



もちろん最初はユーザー登録が必要なので、「Create account」のリンクをクリックします。(この辺りの文言はカスタマイズ可能です。)


ここで、電話番号の入力欄が必須となってしまっています。
これを非表示にするためのカスタマイズ方法がドキュメントには載っているのですが、その通りにやってみても上手く行きませんでした。

検索したところ、UI ComponentのバグとしてGitHubのIssueに上がっているようです。ちょうどこの記事を書いている数時間前にFixがマージされているみたいなので、近日中には解決しそうです。今はとりあえずダミーの電話番号を入力して次へ進みます。


登録フォームをSubmitすると、確認コードの入力画面に移ります。




入力したメールアドレスに確認コードが送られているので、そのコードを入力して「Confirm」をクリックすれば登録完了です。



この時点でAWSコンソールでCognitoのUser Poolを見てみると、確かに1件のユーザー情報が登録されているのが分かります。


バックエンド側を何も作り込んでいないのにここまで自動的に出来上がってしまうのはすごいですね!


最後に上で作成したlogin.jsと同様に認証用のUIをindex.jsページにも追加します。

-----

-----

ここではサインアウトボタンは要らないので削除し、代わりに右上のヘッダー内に「Sign Out」リンクを追加ししました。

サインアウトの処理を実装するには、

import { Auth } from 'aws-amplify';

でAuthクラスをインポートし、リンクがクリックされたら

Auth.signOut();

を実行すればOKです。



次回はバックエンド側のREST APIをAmplifyのサーバーレスな機能を使って追加します!

AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する 




ここまでのソースコードはこちらで確認出来ます!






 

2020年4月16日

Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする

今回は、Next.jsから(SSR無しの)スタティックなSPAとしてエクスポートしたサイトを、Amplify Consoleでホスティングしてみます。


サンプルReactアプリの作成


npm init -y
npm i -S react react-dom next
 


package.jsonはこんな感じになっています。




npm run dev を実行すると開発サーバーが立ち上がるのでブラウザで http://localhost:3000 を開きます。


npm run export を実行すると out フォルダにindex.htmlや404.html などのファイルが出力されます。今回はこれらの静的なHTMLファイルを Amplify Consoleでホスティングすることまで出来れば目的達成です。


amplify-cliのインストール


npm i -g amplify-cli

Amplifyの初期化


amplify configure








amplify init



Amplify Consoleでのホスティング設定を追加


amplify hosting add


上のコマンドを実行して「Continuous deployment...」を選ぶと、ブラウザが開いて接続するGitリポジトリを聞かれます。




amplify.yml という設定ファイルを手動で変更する必要があるので、ダウンロードしてプロジェクトのルートフォルダに保存します。


ここではNext.jsでスタティック・エクスポートした成果物をデプロイしたいので、下のように一部を変更します。(commandsとbaseDirectory)




変更したamplify.ymlファイルをGitリポジトリにコミット・プッシュします。

プッシュした時点で自動的にAmplify Console側でビルド処理が走ります。




ProvisionからVerifyまで全てグリーンになってから画面に表示されているURLをクリックすると、アプリケーションがデプロイされているのが確認出来ます。


今は「Hello!」と表示するだけで何もしないアプリケーションですが、後は通常のReactアプリケーションとして「npm run dev」で実行しながらローカルで開発します。




ひとまずブラウザ上だけで動くTodoアプリを作りました。





このままではブラウザを閉じるとデータが消えてしまうので意味が無いですが、次回以降のエントリではここに「ユーザー認証」と「REST API経由でのDynamoDBへのデータ永続化機能」を追加して行こうと思います。


AWS AmplifyでReactアプリにユーザー認証機能を追加する

AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する




現在のソースコードはこちらで参照出来ます!

https://github.com/mikehibm/amplify-test01/tree/blog-2020-04-15