【個人開発/未経験】エモさたっぷりプロフィール帳アプリ開発してみた【Next.js + Rails API】

## はじめに
はじめまして、ミツと申します！
突然ですが、皆様、**プロフィール帳**ってご存知でしょうか！？
私が小学生の頃（約２０年前）クラス替えや席替えのタイミングで、女子のクラスメートから「これ、書いて～」と1枚、かわいい用紙がよく渡されました。その紙に沿って自分のプロフィールを書き、持ち主に返すという流れがありました。

今回このプロフィール帳をモチーフにしたエモさたっぷりのアプリを開発したのでご紹介いたします！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/a355b8db-39bc-ab90-b715-ec6a6f5b1ff3.png)


※私は現在プログラミング学習中の実務未経験の者であり、技術的な内容などに誤りを含む可能性があります。不適切な記述などがありましたらコメント等で教えていただけると幸いです🙇‍♂️



## 作成したアプリ
##### アプリ名: [りーどみー](https://readmeee.vercel.app)
![top_ogp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/ac706104-6705-632f-426c-9a0757cce830.png)

サービスURL: https://readmeee.vercel.app
GitHub URL: https://github.com/mitsu30/Read_me

## 開発の背景

私が通っているプログラミングスクールでは、受講生同士が交流する**コミュニティ活動が活発**です。
プログラミング学習を教えあったり、モチベーションが下がったときに励ましあったりなど、私自身がコミュニティの強みを存分に活かして、楽しみながら勉強を継続することができています！

しかしながら、**スクールに入学したばかりの方にはコミュニティに参加することに少しハードルがあったりします**。自分の体験や周りの受講生のお話から、理由としては以下のような点が挙げれられます。

- **最初の会話のきっかけがつかむことが難しい。**
- **そもそも周りにどんな人がいるのか知らない。**

「**せっかくコミュニティがあるのに活用できていないのはもったいない...!**」
そんな気持ちでこちらのサービスを開発しました！

このサービスでは**スクールの受講生がコミュニティにスムーズに参加することを手助けすること**を目的としました！

もちろんスクール受講生外の方も使っていただけます！！（＊一部機能がご利用いただけません。）

## サービス概要と使い方
りーどみーについてざっくりと説明いたします！
### プロフィール帳作成機能
①フォームにプロフィールを入力します。
②「つくる！」のボタンを押します。
③ちょっと待ちます。
④**プロフィール帳**の完成です！
[![Image from Gyazo](https://i.gyazo.com/60eddf430a15276f5207d7bc0dde9bab.gif)](https://gyazo.com/60eddf430a15276f5207d7bc0dde9bab)

### X(Twitter)シェア機能
完成したプロフィール帳を**X(Twitter)にシェア**して自己紹介しましょう！！
作成した画像はシェア時のOGP画像に反映されます！
[![Image from Gyazo](https://i.gyazo.com/7df942c2b26b2ed5121b2a5a27c900a6.png)](https://gyazo.com/7df942c2b26b2ed5121b2a5a27c900a6)

### ユーザー機能
**ログイン**すると、以下の追加機能を利用できます
- アプリ内で作成した**プロフィール帳の保存**
- **別のデザイン**のプロフィール帳の作成
- ユーザー登録している**スクール関係者のプロフィール帳の閲覧**(＊一般ユーザーの方はスクール関係者が登録したアバター画像が見えなくなっています)

| ユーザー一覧ページ | ユーザー詳細ページ |
|----------|-------|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/ee378051-21c9-59a2-2af1-24ddcbc39a0a.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/e640330e-0769-979d-24b7-b0e8fdd15c39.png)|

#### ログイン方法
新規登録の手間を減らし、スクール関係者と一般の方を自動的に判別するため、**Firebase AuthとGitHub Organization所属の確認を組み合わせたGitHubログイン**を導入しています。

ログインについては、詳しくは[後述(工夫した点 ①UI・UX)](https://qiita.com/mitsu30/private/9d30da75941e328b541f#uiux)します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/05eebd17-0fa9-e738-ffba-3bf2dfb935c3.png)


## 工夫した点
大きく分けて下記の**３点**を工夫しました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/3f8c4a0f-7b87-d7b1-1355-a2ef09e12039.png)

### ①UI・UX
初学者なりに**わかりやすいUI**と**ストレスフリーなUX**を目指しました。


- **プレビュー機能**
**プロフィール帳の楽しさは、作成の過程にもあります**。記入した内容をすぐに確認できるよう、**画面遷移なしで非同期のプレビュー**を実装しました。もし気に入らなければ、すぐに修正し、再度プレビューを確認することが可能です！

[![Image from Gyazo](https://i.gyazo.com/4ef70e01b842842492fd1b5c53619ad5.gif)](https://gyazo.com/4ef70e01b842842492fd1b5c53619ad5)

- **ローディング画面の実装**
「つくる！」ボタンを押してからプロフィール帳が完成するまでの間、**ユーザーが待ち時間をストレスなく楽しめる**ようなローディング画面を設けました。
[![Image from Gyazo](https://i.gyazo.com/5645a8cb7d0199814a3760a5a5df9ced.gif)](https://gyazo.com/5645a8cb7d0199814a3760a5a5df9ced)

バックエンドで生成した画像をフロントエンドで読み込む時間と、次の画面に遷移する時間に状態を持たせています。
```JSX:ローディングコンポーネント
const [isLoading, setIsLoading] = useState(false);
const [isNavigating, setIsNavigating] = useState(false);

{isLoading && <Loading>まっててね</Loading>} 
{isNavigating && <Loading>もうちょっと</Loading>} 
```

- **自動転記**
プロフィール帳の記入内容が多くなると入力の手間が増えます。そこで、ログインユーザーに対して、**誕生日を基にした十二星座の自動判定**や、一度登録した**アバター画像の自動取得**を実装しました。これにより、**ユーザーの手間を軽減**しました。

[![Image from Gyazo](https://i.gyazo.com/603cdc771cf3192776325dabc3873b32.gif)](https://gyazo.com/603cdc771cf3192776325dabc3873b32)

**画像と文字の合成**、**画像のトリミング・リサイズ**、**画像と画像の合成**には[MiniMagick](https://github.com/minimagick/minimagick)を用いています。
実装の際には以下の株式会社mofmofのsugiii8さんの記事を参考にしました。

:::note info
- [MiniMagickで画像合成してみた](https://tech.mof-mof.co.jp/blog/rails-minimagick-image-composition/)
:::


**どのようなアスペクト比(画像の横幅と縦幅の比率)の画像でも適切に中心部分を切り取る**ように以下のように実装しました。

```ruby:画像のトリミング、リサイズ
avatar_image = MiniMagick::Image.open(avatar_path)
avatar_image.combine_options do |c|
  c.gravity "Center"
  shortest_side = [avatar_image.width, avatar_image.height].min
  c.crop "#{shortest_side}x#{shortest_side}+0+0"
  c.resize "200x200"
end
```

さらに、複数行のテキストを画像に合成する際、フロントエンドから送信された**行数に応じてテキストを配置する座標を調整**しています。具体的なコードは以下のようになっています。
```ruby:複数行のテキストの画像合成
lines = image_text.message.split("\n")
lines.each_with_index do |line, index|
  text_position = case lines.size
  when 1
    "0,#{418 + index * 40}"
  when 2
    "0,#{396 + index * 40}"
  when 3
    "0,#{380 + index * 40}"
  end

  image.combine_options do |c|
    c.gravity 'North'
    c.pointsize '40'
    c.font Rails.root.join('public', 'fonts', 'Yomogi.ttf')
    c.fill '#666666'
    c.draw "text #{text_position} '#{line}'"
  end
end
```

- **ログイン機能**
**Firebase AuthとGitHub Organization所属の確認を組み合わせたGitHubログイン**を実装し、スクール関係者と一般の方を自動的に判別し、**新規登録の手間を減らしています**。

[![Image from Gyazo](https://i.gyazo.com/e475d8b75fb5003b2e5af644d8d24feb.gif)](https://gyazo.com/e475d8b75fb5003b2e5af644d8d24feb)

ログイン機能の実装には**Firebase Auth**を選択しました。その理由は以下の通りです。
- ユーザー体験を良くしたい。
- ユーザー登録のハードルを下げたい。
- セキュリティの知識に不安があるため、個人情報を極力保持しないようにしたい。

:::note info
Firebase Authの詳細な実装方法については、以下のリンクから学ぶことができます。これらはスクールの先輩方が丁寧に詳しくまとめています！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/78e91e31-689d-05d2-eabd-bf0fd60f0e3e.png)

-  [Rails編 - Rails + Next.js + Firebase V9 Authentication で認証付きのCRUDアプリを作る](https://lei900.github.io/22/11/rails-firebase-auth/)
- [【個人開発】駆け出しエンジニアのポートフォリオ作りを手助けするサービスを作りました](https://qiita.com/yusuke_blog1026/items/c68c45790a977cbd0715#3-2-idaas%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E3%82%BD%E3%83%BC%E3%82%B7%E3%83%A3%E3%83%AB%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3)
- [【初心者エンジニアが挑戦】PokeAPIを利用したポケモンのダメージ計算アプリを開発！](https://qiita.com/hagoromo2000/items/9d90734bfbba88e26de8?utm_content=buffer2ddc6&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer#3%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
:::

Firebase Authを通して、GitHubのアカウント名を取得することができます。

```JSX:GitHubのアカウント名を取得(一部抜粋)
const loginWithFirebase = async (method) => {
  const getResult = () => {
      return signInWithPopup(auth, getProvider(method));
  };

  const result = await getResult();

  if (result) {
    const user = result.user;
    const details = getAdditionalUserInfo(result);
    return { user, details };
  }
};

const userDetails = {username: details.username};
```

さらに、ユーザーが特定のOrganizationに所属しているかどうかを確認するために、バックエンドでは[Octokit](https://github.com/octokit)を使用しています。[Octokit](https://github.com/octokit)はGitHubのAPIを簡単に使用できるRubyのライブラリです。
```ruby:Organization所属の確認
def self.organization_member?(github_user_id)
  client = Octokit::Client.new(access_token: ENV['GITHUB_ACCESS_TOKEN'])
  is_member = client.organization_member?('organization_name', github_user_id)
  is_member
end
```

### ②プロフィール帳の世界観
プロフィール帳の**ファンシーな世界観**を表現するためにアプリ内の**フォント**や**配色**には細心の注意を払いました。また、ユーザーが自己紹介する内容について、**個人的な情報を公開する際の心理的なハードル**を考慮に入れました。[後述(開発期間 ①プレリリースまで（2023年5月17日〜2023年6月1日）)](https://qiita.com/mitsu30/private/9d30da75941e328b541f#%E5%A4%A7%E5%9E%8B%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88%E3%81%BE%E3%81%A72023%E5%B9%B46%E6%9C%882%E6%97%A52023%E5%B9%B47%E6%9C%885%E6%97%A5)しますが、プレリリース時のフィードバックをもとに、ユーザーがどの程度の情報を公開するのか考察を行い、反映させました。

現在使用できるプロフィール帳のテンプレートは**4つ**になります。(＊一般ユーザーの方は上2つのみになります。)

| みにまむ | ベーしっく |
|----------|-------|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/9e3c0370-b3ae-5dbf-5cc7-e3ef7c717803.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/a355b8db-39bc-ab90-b715-ec6a6f5b1ff3.png)|

| すくーる | あにばーさりー |
|----------|-------|
|![使ってます！MVP用 (1200 × 630 px) (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/462a8b5e-c127-015d-fe76-8a993e83a080.png)|![使ってます！MVP用 (1200 × 630 px) (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/7d0e55e4-116c-7073-355f-24e01cf6525c.png)|

（＊大人の事情で一部モザイクが入っています）

- **フォント**
アプリのイメージとマッチした[Yomogi](https://fonts.google.com/specimen/Yomogi)を採用しました！
Googleフォントを用いて簡単に導入できます。

```JSX
<Head>
  <link
    href="https://fonts.googleapis.com/css2?family=Yomogi&display=swap"
    rel="stylesheet"
  />
</Head>
```

- **配色**
アプリで使用する色を主に**暖色系のパステルカラー**を中心に選択しました。
カラーコードは[ノグチデザインさん(@n_seitan)](https://twitter.com/n_seitan?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor)を参考にしました。
また、**#000000（真っ黒）の使用は避けました**。


- **プロフィール帳作成画面**
プロフィール帳の作成画面は、**本のページ**をイメージしてデザインしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/502c4bce-4cf1-d545-6fb8-07f31677a6ca.png)

- **内容の方針**
パーソナルな部分を不特定多数の人に公開するため、心理的なハードルがあるかと思います。そこで、自己紹介はするけれども、**個人を特定するような情報は載せない**ように内容に注意を払いました。


### ③プライバシー保護
ユーザーのプライバシーを尊重するため、アプリ内ではプロフィール帳の**公開範囲**を設定できます。公開を選ばないユーザーのプロフィール帳は外部から見えないようになっており、さらに**動的OGPの表示もオフになる**設計になっています。公開範囲の設定確認はマイページからできます。

| 公開範囲の設定 | マイページで確認 |
|----------|-------|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/ba9619db-cc91-0e17-5412-310a1e522b5d.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/38426b90-693b-8fde-2e9d-54017d85aa1b.png)|

ユーザーの権限に応じて、適切な公開範囲のプロフィール帳を取得するように実装しました。
（冗長となってしまいましたが...）
```ruby:一部抜粋
def build_profiles_data
  user_communities = @user.membered_communities.map(&:id) 
  @showed_user.profiles.with_attached_image.select do |profile|
    profile.privacy == '****' || (profile.privacy == '****' && (user_communities & profile.open_ranges.map(&:community_id)).any?)
  end.map do |p| 
    {
      id: p.id,
      uuid: p.uuid,
      image_url: p.image.url,
    }
  end
end
```


公開範囲を設定しているプロフィール帳をX(Twitter)にシェアする際には**アプリ内でデフォルトにしている画像がOGP画像に設定される**ように実装しています。

[![Image from Gyazo](https://i.gyazo.com/1efddf9af4ce274bfe5f72ace49a0850.png)](https://gyazo.com/1efddf9af4ce274bfe5f72ace49a0850)

ユーザーがプロフィール帳に設定している公開範囲に応じて、X(Twitter)にクロールさせる画像のURLを変更させています。

```JSX:動的OGPの表示のオンオフ
let imageUrl;
if (profileImage.privacy === '****' || profileImage.privacy === '****') {
  imageUrl = alternativeImageUrl;
} else {
  imageUrl = profileImage.image_url;
}
```
:::note info
動的OGP設定についてはスクールの先輩が丁寧にまとめており、こちらを基に勉強しました！また、OGP画像の確認をするためのサイトもあわせて載せておきます！
- [【初心者向け】Next.js + Rails APIで認証付きCRUDアプリ作って学んだこと](https://qiita.com/lei90/items/067421b5290734b3a634#next-seo%E3%82%92%E4%BD%BF%E3%81%86)
- [Rails × Vue.jsのSPAでTwitterのOGPを動的に反映させる方法](https://qiita.com/o83184206/items/409736eebe2f92d36e86)
- [ラッコツールズ](https://rakko.tools/tools/9/)
:::


## 主な使用技術
## フロントエンド
- Next.js 12.2.3
- React 18.2.0

### 主要なライブラリ 
- @mui/icons-material & @mui/material 5.11.16 & 5.13.1
- @emotion/react & @emotion/styled 11.11.0
- axios 1.4.0
- next-seo 6.0.0
- nookies 2.5.2
- notistack 3.0.1
- react-paginate 8.2.0

### 技術選定理由
- **Next.js**
**ストレスのないUX**を目指し、**サクサク応答するUI**を実現したかったため、Next.jsを選択しました。また、メイン機能である**非同期のプレビュー機能**を実装できる、かつ、X(Twitter)シェア機能において、作成したプロフィール帳を**動的にOGP画像に反映**させるためにサーバーサイドレンダリング(SSR)をサポートしているNext.jsが最適かなと考えました。

- **MUI**
アプリの設計段階で、フロントの世界観が重要となってくると考えていました。そのため、**豊富なデザインコンポーネント**を提供し、比較的手軽に実装できそうかつ、カスタマイズの自由度が高そうなMUIを採用しました。

https://mui.com


## バックエンド
- Rails(APIモード) 6.1.7
- ruby 3.2.2
- PostgreSQL

### 主要なgem
- minimagick 4.12.0
- rack-cors 2.0.1
- jwt 2.7.0 
- aws-sdk-s3 1.122.0
- octokit 6.1.1
- kaminari 1.2.2

### 技術選定理由
- **Rails**
私が通っているプログラミングスクールでは、カリキュラムはRailsがメインとなっています。初心者ながらに使い慣れており、**実装スピードを上げられる**と考え、採用しました。

- **PostgreSQL**
後述するFly.ioで**無料**で使用できたため、採用しました。


## インフラ・その他
- インフラ
  - Vercel
  - Fly.io
  - Amazon S3

- その他
  - Firebase Authentication
  - GitHub API
  - cron-job.org

### 技術選定理由
- **Vercel**
Next.jsの開発者たちが作ったプラットフォームであり、**Next.jsとの相性がいい**と考え、採用しました。GitHubとの連携がとてもシンプルであり、pushしただけで自動的にデプロイされる点も**初学者には易しかった**です。ドキュメントもわかりやすく、トラブルが発生した際もスムーズに解決できました。

https://vercel.com/dashboard

- **Fly.io**
**無料で利用できる**ため採用しました。また、ドキュメントを確認すると手軽なコマンドが多数準備されており、**初学者にもわかりやすかった**点も理由の一つです。

https://fly.io/docs/


- **Amazon S3**
Active Storageがデフォルトでサポートしている点と無料枠が大きかったので採用しました。

### Fly.ioのスリープモードについて
- **cron-job.org**
Fly.ioの無料プランでは短時間でスリープモードに入ってしまうので下記のサイトからバックエンドにリクエストを送っています。JSONを返すだけのメソッドを設定しています。

```ruby
 def wakeup
   render json: { message: 'Server is awake' }
 end
```

https://console.cron-job.org/

## ER図
ER図は以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/00245ba3-6ae8-e509-b7fa-c991e528bbe1.png)

## 開発期間

開発を始める前に以下の技術のキャッチアップを**それぞれ1週間ずつ合計で約3週間**行いました。
- JavaScript　　　
- React
- Next.jsとフロントエンドとバックエンドの連携

アプリ全体の作成期間は**約1ヶ月半**、内訳としてはスクール内での**プレリリースまでが約2週間**、そこから**大型アップデートまでが約1ヶ月**となっています。

### ①プレリリースまで（2023年5月17日〜2023年6月1日）
現場実装している「おためし」の機能(**ユーザーと紐づかないプロフィール帳の作成とX(Twitter)シェア機能**)のみを実装してX(Twitter)上で公開しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/cfd10c8a-c249-4392-9825-9f450d2d9c77.png)

プレリリースまでで意識していたことは、**最低限の機能のみを実装して早く人様に出せる形にすること**でした。プレリリースを急いだ理由は以下です。

- **UI・UXに関してユーザーのフィードバックが欲しかったから。**
設計の段階からフロントの世界観が大事になってくるアプリと考えていました。自分なりには工夫しましたが、**プロフィール帳の世界観を表現できているのか？** **自己満足なUI・UXになっていないか？** といったことがわからなかったたため、**ユーザーの声を早めに確かめたい**意図がありました。不評だったら実装の方向性を変えようと考えていました。幸いなことにプレリリース時のアンケート(以下の円グラフ)でたくさんの好評をいただき、方向性は変えずに実装を進めることにしました!
特にデザインがかわいいという声をたくさんいただけて嬉しかったです！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/c83e52ae-445e-667e-13aa-a6255d17e9c4.png)


- **ユーザーがどの程度を情報を公開するのか確認したかったから。**
自分のプロフィールを不特定多数の人に公開するという点に心理的なハードルがあるかと思います。プレリリース時のプロフィール帳には自由記入欄を設けており、**ユーザーがどの程度の情報なら公開するのか確かめたい**意図がありました。**プレリリース時にユーザーに記入していただいた内容を考慮に入れて、追加のテンプレートに反映させています**。

### ②大型アップデートまで（2023年6月2日〜2023年7月5日）
「おためし」機能以外の機能をこの期間に実装しました！
実装中に特に苦労したのは上述したMiniMagickを用いた画像合成ロジックです。
参考文献や公式ドキュメントを読んで、**一つ一つのコードが何の役割を果たしているのかを丁寧に理解**していき、実装に結びつきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531138/9217cd29-1cd2-a2a4-360a-4403ae499453.png)

## 今後のりーどみー
課題点や今後実装したい点については以下のとおりです。

**1.  認証周りの機能追加**
退会機能が未実装なので、ユーザーに安心して利用していただけるように対応したいと考えています。

**2.  テストコードの追加**
現状テストコードがほとんど書けていない状態ですので、順次追加していくつもりです。

**3.  新たなプロフィール帳テンプレートの追加**
一度使っていただいた方にも再度楽しんでもらうためにも、検討中です。


## 終わりに
これまで練習アプリの類すら作成したことがなく、今回初めてサービスを1から作りましたが、本当にたくさんの学びありました！
また、実装を急いでいたため緊張感がありましたが、**楽しみながら開発できました！**

特に反省した点が、**アプリの設計があやふやなまま開発を始めてしまった点**です。プレリリースまで約2週間かかっていますが、最初の1週間は実装に迷走しており、ほとんど進捗0でした。プロフィール帳の作成を別の方法で考えていたのですが、思い描いていた実装が全くできずに詰まっていました。スクールの校長や講師の方アドバイスを元に方針転向することができました。アプリ設計の時点でもう少し詳細に技術調査をして、実装のイメージが固めておくべきでした。

本記事が少しでも皆さんの参考になれば幸いです！
最後までご覧いただき本当にありがとうございました🙇‍♂️