【QiitaHackathon2024】『Halico』とその技術構成について

# 概要
先日開催されたQiita主催のハッカソンにて、HalicoというWebアプリを制作しました。

この記事では、制作したアプリとその技術構成などについて書いていきます。

ハッカソン自体の参加記録は以下のnoteに書いています。

https://note.com/yuma9208/n/n189434a9bcb7?sub_rt=share_pb

# 参加メンバー
以下の三名で参加しました。
- ゲームクライアントエンジニア(自分)
- ゲームクライアントエンジニア
- インフラエンジニア

# Qiita Hackathon 2024 について
Qiitaが主催するハッカソンで、9月21日から9月22日にかけて開催されました。

テーマが当日に発表され、そのテーマに沿ったアプリケーションを制作する形式のハッカソンです。

詳細は以下のページを参照ください。

https://qiita.com/official-campaigns/hackathon/2024

# テーマ『オープン』
今回のテーマは「オープン」でした。

テーマ発表後にメンバーでアイデアスケッチを行い、**『お互いの趣味嗜好が一致したらオープンになるアプリ』** というアイデアを元に制作を始めました。

# 背景
オフラインの交流会などで会話をする際、お互いがオープンにならないといけませんが、共通の話題でないときに盛り上がりづらいという問題があります。

そこで、会話の最初から共通で話せる話題がわかれば会話が盛り上がるのではないかと考え、その方向性でアプリを制作することにしました。

# Halico -Have a lively conversation-
![【📮】Qiita Hackathon 2024.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483241/9982d894-c2d6-68be-553a-0ed45feeec81.png)


## 使い方
1. 質問に答える形で話題（単語）を登録
2. QRコードを読み取る
3. 相手の話題一覧が表示される
4. 自分の回答と一致している単語がある場合、その単語のみが表示される
5. 一致していない単語には、自分の回答との類似度が表示される

以上が簡単な使い方です。

## 機能
Halicoの主な機能について説明します。

### イベント作成
Halicoはイベントごとに質問を作ることができます。

イベントによって参加者の性質が異なるため、共通の質問を作るのではなく、イベントごとに質問を作成するほうが、より盛り上がる会話ができると考えました。

まずイベントの名前と質問を登録します。
<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483241/e16264d8-dc91-ed80-b010-ce6c9925b8ef.png">

作成後は一覧画面からURLをコピーして、参加者に共有します。
<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483241/fbffbb06-d416-0396-3362-5016f1ebd2f9.png">

### 回答登録
イベント作成者から共有されたURLにアクセスすると、QRコードの表示された画面が表示されます（QRコードに関しては後で説明します）。
<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483241/83d4c65a-7fef-a3ad-118a-0d4f901389ac.png">


QRコードの下にある「回答する」ボタンを押すと、質問に回答する画面へ遷移します。
この画面で参加者は単語を登録することができます。
<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483241/ef23255a-227f-aebd-c365-4333c174a421.png">

複数の単語を登録する場合は、改行区切りで入力してもらうこととしました。

理由としては、スマートフォンでの入力をメインに考えていたため、カンマなどよりも改行のほうが入力しやすいと考えたためです。

### 回答確認
登録ボタンを押すと、最初に表示されたQRコード画面へ戻ります。

QRコードを読み取ってもらうことで、相手に自分の回答を共有することができます。
相手が同じ単語を登録している場合、その単語は最初から見えている状態になります。

一致していない単語については、自分の回答との類似度が表示されます。
<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483241/c9342fbe-437d-9a65-3a38-f3f687dde45f.png">

類似度を表示した意図としては、完全一致ではなくても同じものを指しているケースを考慮したためです。

例えば、「読書」と「本を読む」は文字列上完全一致ではありませんが同じ話題を指していると考えられるため、それらを一律で非表示にするのではなく、類似度を表示することで、会話の幅を広げることができると考えました。

もし別の話題を指していたとしても、似たような話題であればそれを元に会話を広げることもできると考え、この機能を実装しました。

隠れている単語の横にはボタンが表示されており、押すことでその単語が気になっていることを相手に伝えることができます。

# 技術構成
利用した技術は以下の通りです。
![【📮】Qiita Hackathon 2024 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483241/dd248ece-314c-3537-d906-801ee1d33c59.png)

それぞれどのあたりでどのように利用したかについて説明します。

## Next.js(App Router)
フロントエンドにはNext.jsを採用しました。
全員が共通して使えるフレームワークであることが大きな理由です。
viteを使ってSPA構成にすることも検討しましたが、認証にNextAuthを使いたかったため、Next.jsに決めました。

App Routerを使うことにこだわりはありませんでしたが、コロケーションがデフォルトでやりやすいことから、App Routerを使用しました。

## Firestore
データベースにはFirestoreを採用しました。
この技術も全員が共通して使える技術であることが大きな理由です。
また、NoSQLであるためデータ構造が柔軟であることも採用理由の一つです。

## OpenAI API
類似度を計算するため部分はOpenAI APIを利用しました。
最初はChatCompletionsを使おうと考えていましたが、Embeddingsの方が類似度を計算するのに適していると考え、Embeddingsを使うことにしました。
コストもEmbeddingsの方が安く、ハッカソン終了時点で1円程度しかかかっていませんでした。

## Cloud Run
Next.jsのデプロイはCloudRunを使いました。
インフラ担当のメンバーが使い慣れていることが大きな理由です（多分）。
最初はVercelでサクッとやってしまおうかという話もしていたのですが、Firestoreを使うということはGoogle Cloudを使うことになるため、それならデプロイもそちらに寄せようとなりました。

# 工夫した点
開発を進めるにあたって工夫した点について説明します。

## デザイン
並行作業をするにあたって、全画面のデザインは必要です。
ただ、最初からデザインを凝りすぎてしまうと、途中の方針転換などが発生した際に修正が大変になります。
そのため、最初は情報やボタンの配置を決める程度に留め、デザインの詳細は後回しにしました。

（序盤で作成したワイヤーフレーム）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483241/32e756b9-ea64-c569-b4a0-014cbf2d460e.png)

## コンポーネント
今回のハッカソンにおいては、機能の開発を素早く進めることを重視しました。
そのため、CSSの記述量を抑えつつ良い感じの見た目を担保するためにChakraUIを利用しました。
しかし、後々デザインを凝ったものに変更したくなった際、ChakraUIのコンポーネントをそのまま使うと変更点が大きくなってしまいます。

そこで、ChakraUIのコンポーネントをラップしたコンポーネントを作成しました。
例えば、ChakraUIの`Button`コンポーネントをラップした`Button`コンポーネントを作成するというような感じです。

こうすることにより、後でデザインが変更になったとしてもPropsを変更せずに中身だけ変えることで対応できるようになります。

# 開発の振り返り
今回のハッカソンでは、バックエンドをたてずにNext.jsのみで開発を行いました。
同じコードベースを触ることで機能開発がスムーズに進められたので、この構成自体はよかったと思います。

しかし、一つの機能開発にある程度時間がかかり、全部の画面が完成するまでの時間（デザイン修正に取り掛かるまでの時間）は比較的長かったかなと思います。

また、今回はそこまで問題になりませんでしたが、同じようなデータを2画面以上で利用する際にそれぞれがデータを取ってくる処理を書いていたので、APIとしてバックエンドをたてて切り出したほうがよかったなと思うこともありました。
（こちらに関してはRoute Handlersを利用すればよかったかも）。

最近はバックエンドをたてる構成で多くやっていたため、いい知見を得ることができました。
本戦では今回の経験を踏まえて、よりスムーズな開発ができるようにしたいと思います。