連載目次
| 回 | 内容 |
|---|---|
| #1 | 企画・課題発見 |
| #2 | 要件定義 |
| #3 | 設計(この記事) |
| #4 | データ処理・ロジック実装編 |
| #5 | UI・画面実装編 |
| #6 | テスト・CI/CD構築編 |
| #7 | 開発中に遭遇した技術的問題 |
| #8 | 完成・振り返り |
はじめに
前回は要件定義として、最初の版(MVP)に入れる機能と、「アカウント登録なし」「データはブラウザに保存」などの条件を決めました。
今回は設計編です。使う技術、全体の構成、データの形、画面のデザインをどう決めたかを書きます。
技術選定
| 分類 | 技術 | 選んだ理由 |
|---|---|---|
| フレームワーク | Next.js 16(App Router) | 画面の作成とページ遷移をまとめて扱える。Vercel と相性がよく、公開しやすい |
| UI | React 19 | 画面を部品(コンポーネント)に分けて作れるので、後から機能を追加・修正しやすい |
| 言語 | TypeScript | データの形の間違いを、動かす前に見つけられる |
| スタイル | Tailwind CSS | 部品の中で見た目の指定が完結し、デザインの調整を素早くできる |
| アイコン | lucide-react | 軽く、見た目のそろったアイコンを必要な分だけ使える |
| データ保存 | ブラウザの localStorage | サーバーとデータベースを持たず、登録なしで使えるようにするため |
| テスト | Vitest / React Testing Library | 部品ごとのテストを速く実行できる |
| 公開 | Vercel(無料プラン) | GitHub と連携するだけで自動で公開される |
データベースを使わなかった理由
最初は、データの保存にデータベースのサービス(Supabase)を使う想定でした。しかし要件定義で「アカウント登録なしで使える」と決めたので、利用者ごとにデータを分けて持つ必要がなくなりました。
データを置く場所は、その人のブラウザで十分です。そこでデータベースは使わず、ブラウザに最初から備わっている保存場所の localStorage を使うことにしました。サーバー側の処理がなくなるので、構成もその分シンプルになります。
全体の構成
サーバーを持たないので、アプリの処理はすべてブラウザの中で完結します。フォルダは役割ごとに3つに分けました。
app/ … URL と画面の対応(ルーティング)
page.tsx → / トップページ
app/page.tsx → /app 登録画面(ホーム)
practice/[id]/page.tsx → /practice/ID 練習画面
components/ … 画面の部品(ホーム画面、練習画面、メニューなど)
lib/ … 画面を持たない処理(データの型、保存、テキストの加工)
- app:どのURLでどの画面を出すかだけを書き、中身は components の部品に任せる
- components:画面の見た目と操作
- lib:画面に依存しない処理。データの形(型)、localStorage への保存と読み出し、キーワードの抜き出しなど
画面を持たない処理を lib に分けておくと、画面を動かさずに処理だけをテストできます。テストの話は #6 で書きます。
画面遷移
データの形(状態構造図)
データベースを持たないので、ER図の代わりに、アプリが扱うデータの形をクラス図で整理しました。
設計で決めたポイントは次のとおりです。
- Text(登録した文章1件)が中心:元の文章(rawText)と、分割したあとのブロック(chunks)の両方を持つ。分割結果だけを保存すると、元の文章に戻せなくなるため
- フォルダとの関係はテキスト側だけが持つ:テキストが「どのフォルダに入っているか」(folderId)を持ち、フォルダ側はテキストの一覧を持たない。両方に持たせると、移動や削除のたびに2か所を書き換える必要があり、食い違いが起きやすくなるため
- 未分類は folderId が null:どのフォルダにも入っていない状態を、空の文字ではなく null で表す
-
習熟度(status)は3つの値に限定:
"new" | "learning" | "mastered"のように TypeScript の型で取れる値を絞り、打ち間違いを防ぐ -
保存するのは localStorage の2つのキー:テキスト用とフォルダ用。キー名の末尾に
-v1を付け、将来データの形を変えたときに古い形式と区別できるようにした
ブロック(Chunk)や文ごとの表示状態(revealed など)は、長文を分割して少しずつ覚える機能のための設計でした。分割機能はその後廃止しています。その影響は #7 で書きます。
画面のデザイン
ワイヤーフレーム
最初に、画面ごとに何をどこに置くかを、罫線を使った文字の図(ワイヤーフレーム)で決めました。主な方針は次のとおりです。
- ホーム画面:新しい文章の登録フォームを主役にする。保存済みの一覧は開閉式にして、最初は閉じておく
- メニュー:画面右上のボタン(ハンバーガーメニュー)から開き、フォルダの管理と表示の絞り込みをまとめる
- 練習画面:習熟度の選択、3つの見せ方の切り替えタブ、本文の順に並べる
スマホ対応と配色
- スマホを基準に作る(モバイルファースト):すきま時間にスマホで練習する使い方を想定しているため
-
切り替えは1か所だけ:Tailwind CSS の
sm(幅640px以上)で、PC向けに入力欄を広げるなどの調整をする -
配色:紺・インディゴ系にそろえた(メイン
#1f2f52/アクセント#3a5a9c/背景#e7edf7)。落ち着いて集中できる色を選んだ
V0で画面デザインを作る
ワイヤーフレームをもとに、V0(v0.dev)で画面のデザインを作りました。V0は、作りたい画面を文章で伝えると、React と Tailwind CSS のコードで画面を作ってくれるサービスです。
作るときに工夫したのは次の2点です。
- 画面ごとに別のチャットで作る:ホーム画面・メニュー・練習画面を1回の指示でまとめて頼むと、情報が多すぎて内容がぶれるため、3つに分けた
- ワイヤーフレームの図をそのまま指示に入れる:罫線の図を貼り付けて、要素の配置を正確に伝えた
一方で、できたコードの中身を確認すると、穴埋めとキーワードのモードが 「覚えるべき言葉をあらかじめ手で指定しておく方式」 になっていました。設計では、利用者が貼り付けた文章から自動で言葉を見つける方式にすると決めていたので、この部分は使わず、自分で実装し直すことにしました。自動で見つける仕組みは #4 で書きます。
V0のコードは、見た目の部分だけを取り込みました。取り込むときは「まず見た目のコードを入れ、そのあとに動きの処理を書く」という順番を守っています。見た目と処理を同時に変えると、うまく動かないときに、原因がどちらにあるのか分からなくなるためです。
まとめ
- Next.js・React・TypeScript・Tailwind CSS を使い、データはブラウザの localStorage に保存する構成にした
- 登録なしで使うと決めたので、データベースは使わなかった
- app(URL)・components(画面)・lib(処理)の3つに役割を分けた
- データの形はクラス図で整理し、フォルダとの関係はテキスト側だけに持たせた
- 画面デザインはV0で作り、見た目だけを取り込んで、処理は設計どおり自分で実装した
次回はデータ処理・ロジック実装編です。lib フォルダの中身、特に文章からキーワードを自動で見つける仕組みを書きます。