1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

教育現場で使われている席替えアプリを転職ポートフォリオとして作った話 ——Next.js 16 × Supabase

1
Posted at

はじめに

初めまして。未経験からエンジニアへの転職を目指しているりおたろと申します。私自身、教育現場に長く関わってきた経験があります。

現場の課題を解決したいという思いもあり、今回、転職の際のポートフォリオとして席替えアプリ「Seat Tree」を作成しました。クラスの生徒一人一人に配慮をしながらワンクリックで席替えができるアプリです。

友人である教員のために元々は作成したものですが、口伝いに他の教員にも広まっていていき、今ではたくさんの教員の方に使用していただいています。
アプリの紹介だけでなく、開発の背景や技術的な工夫についてもまとめましたので、ぜひ最後まで読んでいただけると嬉しいです。

Qiita用.gif

開発の背景

学校現場とは

アプリ作成にあたって、教員である友人とも学校現場での業務の多さについて話し合いながら、席替えの大切さと席替えの難しさを言語化してみました。

席替えの大切さ

・教室の座席は学校生活の中心
・配慮のある座席が組めていると、授業や休み時間や給食などの時間に、子どもたち自身も安心して楽しく過ごせていることが多い
・毎月1回など定期的に実施を約束すると、席替えを楽しみに過ごしている子どもも多い
・配慮をして席替えを行えると、保護者からの信頼にもつながる

席替えの難しさ

・生徒の実態や保護者からの要望をもとに実施するので、単純にくじ引きでは決められない
・手動の入れ替えでは完全にランダムにできない
・席替えアプリもあるが、配慮をして席替えができず、結局手動に戻る
・全員の配慮事項を見ながらしていると時間がかかる
・過去の席替えを子どもたちはよく覚えているため履歴の管理が大変
・クラスの席替え以外にも会議、保護者対応、行事準備、毎日の授業準備など放課後の業務が多く、時間をかけても煩雑になりがちでミスが起こる

実態を踏まえて

教員である友人へのヒアリングをもとに、必要な機能を要件として整理しました。この要件をもとにアプリの機能を決めていきました。

・視力や生徒同士の相性など生徒の配慮も登録ができる
・配慮することを踏まえたうえ、ボタン一つで完全にランダムで席替えができる
・最後は手動でも座席を動かすことができる
・過去の席替えの履歴を見ることができる
・配慮したことを守れていない時にすぐ気がつくことができる

アプリ概要(機能一覧)

  • 自動席替え生成(配慮条件の考慮)
    ボタンをクリックすると生徒ごとの「前列希望」や「相性の悪い生徒(隣接不可)」を考慮したランダム配置アルゴリズムを採用

ランダム席替え.gif

  • ドラッグ&ドロップによる直感的な微調整
    自動生成後、特定の生徒間の席を手動で簡単に入れ替えることが可能

ドラッグ&ドロップ.gif

  • Excelファイルからの名簿インポート
    Excelの名簿データをそのままアップロード・解析して生徒一覧に登録可能
    もちろんアプリ内手動で名前を入力して登録も可能

生徒名簿.gif

  • 名簿から配慮の設定
    前列の配慮や相性の悪い生徒同士をクリックで簡単に設定
    配慮が必要なくなっても簡単に外すことが可能

配慮し直し.gif

  • 配慮できていない生徒を視覚的に分かりやすく表示
    設定した配慮がなされていないと、黄色に変わり、揺れて目立つように

座席違反.gif

  • 教室レイアウトのカスタマイズ
    列数や総座席数の自由な変更や、「前列」と見なす範囲の設定、使用しない座席の設定など、各教室の環境に合わせたレイアウト設定

座席設定.gif

  • 席替え履歴の保存機能
    過去の席替えデータを履歴としてクラウドに保存。いつでも過去の座席状態をプレビュー可能

座席保存.gif

  • 座席表の印刷機能
    作成した座席表をそのままプリンターで印刷したり、PDFとして保存するための専用出力

印刷.gif

  • ユーザー認証とデータ永続化
    Supabaseを利用したセキュアなログインと、先生ごとのクラスデータ・履歴のデータベース保存
    生徒の情報は重大な個人情報にあたるため、セキュリティには十分配慮

  • 更新履歴の通知
    アプリのバージョンアップや改善情報をユーザーに届ける更新履歴ページ

更新履歴.gif

使用技術スタック

カテゴリ 技術
フレームワーク Next.js 16 (App Router)
ライブラリ React 19
言語 TypeScript 5
スタイリング Tailwind CSS 4
状態管理 Jotai
バックエンド / DB / 認証 Supabase
ドラッグ&ドロップ @dnd-kit/react
Excelデータ処理 xlsx
印刷処理 react-to-print
アイコン Lucide React
フォント Kiwi Maru / Zen Maru Gothic
バージョン管理 Git / GitHub
エディタ Visual Studio Code
デプロイ Vercel

各技術スタックの選定理由

1. フロントエンドフレームワーク・言語

Next.js 16 (App Router) & React 19

・名簿管理、座席設定、履歴管理など、機能ごとに独立した画面を持つ本アプリにおいて、App Routerのディレクトリ構成はコードの見通しを良くするため

サーバーコンポーネント(RSC) により、データ取得をサーバー側で完結させ、クライアントへの不要なJavaScriptの転送を抑えるため

・Reactは、座席の配置や条件変更など、状態によって複雑に変化するUIを効率的に構築するために最適であるため

TypeScript 5

・本アプリでは「生徒(配慮条件、相性)」「座席」「教室配置」といった複雑なオブジェクトが絡み合い、これらを厳格な型で定義することで、プロパティの参照エラーなどを防ぎ、堅牢なアプリケーションを構築できるため

・Supabaseからデータベースのスキーマ型(database.types.ts)を自動生成し適用することで、DBからフロントエンドまで一貫した型安全性を担保するため

2. バックエンド・認証・データベース

Supabase

・教員が扱う「生徒の氏名」や「人間関係の配慮」は機密性の高いデータであり、SupabaseのRLS(Row Level Security) を活用することで、教員ごとにデータを完全に分離しているため

・認証システム(ログイン/ログアウト)やデータベース構築をBaaSに任せることで、アプリの核である席替えアルゴリズムに集中することができるため

3. 状態管理

Jotai

・席替えアプリでは「特定の生徒をドラッグして移動する」「空席を設定する」など、細かい状態更新がよく起こります。Jotaiは、変更があったコンポーネントのみを再レンダリングするため、パフォーマンスの低下を防ぐため

4. スタイリング

Tailwind CSS 4

・CSSファイルを行き来することなくコンポーネント内でスタイリングが完結するため、高速でスタイリングが完結し、開発スピードが向上するため

・「前列指定の席」「相性の悪い生徒が隣接した際のエラー表示」など、条件によってスタイルを切り替える処理が、テンプレートリテラルを用いて直感的に記述できるため

5. 主要な機能拡張ライブラリ

@dnd-kit/react (ドラッグ&ドロップ)

・自動席替え後に先生が手動で微調整できるD&D機能は本アプリの重要な機能の一つ。@dnd-kitは状態管理とDOM操作が分離されており、今回のような複雑なグリッドレイアウト(座席表)での移動も、軽量かつカスタマイズ性高く実装できるため

xlsx (Excelデータ処理)

・教育現場では一般的に名簿管理にExcelが使われ、ブラウザ上で直接Excelファイルを解析し、手入力の負担なく、生徒データとしてインポートできるため

react-to-print (印刷処理)

・ブラウザに表示されているReactコンポーネントの見た目をそのまま印刷ダイアログに渡せるので、座席表が紙媒体で必要な教員のニーズを満たすことができるため

開発で工夫した点

ゲストログインの実装

教員である友人だけでなく、学校現場でも広く使ってもらいたいという思いから、本登録をせずとも使用できるようになっています。ゲストユーザーであり、登録されている生徒が0人であれば、テンプレートの生徒が追加される仕様です。これは、配慮ある席替えをすぐに体験してほしいという思いから実装しています。

席替えを体験した画面からそのまま本登録へ誘導する導線も設けており、スムーズに移行できるようにしています。

座席設定をローカルストレージに保存し、毎回の手間を省く

教員によって教室内の座席の配置が大きく異なります。座席数、行数、使用しない座席など、席替えのたびに設定し直すと時間がかかってしまうため、ローカルストレージにこれらの状態を保持し、スムーズな席替えの体験を意識しています。JotaiのatomWithStorageを使って実装しています。

保守しやすいファイル構成・コンポーネント設計

Next.jsの特徴であるファイルルーティングを活かしたファイル構成を意識しています。また、1ファイル100行を目安にコンポーネントを分割しており、1つのファイルが単一の責務を持つよう意識しています。

また、ファイルごとに、以下のように役割を分けています。
・providers→認証情報の取得やデータの取得の大枠
・store→グローバルステートの管理
・utils→席替えロジックや生徒の状態によって変わるスタイルの管理
・libs→supabaseのDBとの連携やプロジェクト全体におけるデータの型の管理

データ取得範囲を絞り、UXを損なわない設計に

ユーザーの体験を損なわないように、データを取得するタイミングは、データが必要なページのみに絞っています。

const WithHeaderLayout = ({ children }: { children: React.ReactNode }) => {
  return (
    <div className="min-h-screen bg-wood-50 text-wood-900 pb-20 font-sans">
      <Header />
      <DataProvider>
        <main className="max-w-7xl mx-auto px-4 pt-6">{children}</main>
      </DataProvider>
    </div>
  );
};

ヘッダーはデータ取得と切り離すことで、データ読み込み中もヘッダーが即座に表示されます。

席替えの試行回数をテストしながら実装したこと

教員の配慮がなされたベストな座席を出力するために、席替えのロジックでは、最大500回の試行がされるようになっています。この500という数字が適切なのかを実際に手動でテストをしながら実装しました。詳細は以下の記事にまとめています。

ドラッグ&ドロップの実装をオーバーレイの表示をしない選択をとったこと

ドラッグ&ドロップで本来は実装したいスタイルがありました。以下のような挙動のドラッグ&ドロップを実装したかったのですが、配列のメソッドであるmapとの書き方と相性が合わず、オーバーレイの表示をしない選択をしました。@dnd-kit/reactのようなライブラリを導入する際は既存のコードとの関係性を意識しないといけないという学びにもつながっています。

偽ドラッグ.gif

詳細は以下の記事にもまとめていますので、ご覧下さい。

proxy.tsを使ったページルーティングの工夫

認証がないと入れないページには、認証チェックが必要です。各ページに認証がされているか確認するコードを書いていると、冗長になるので、URLがリクエストされてからレスポンスが返ってくる前に実行されるミドルウェアであるproxy.tsを置いています。

このコードには、認証が必要なページへの不正アクセスをproxy.tsで一元管理しています。リクエスト時にCookieから認証情報を読み取り、未認証の場合はログインページへリダイレクトします。

以下の記事に詳細な学びを書いていますので、ご覧下さい。

苦労した点・どう乗り越えたか

React SPAからの書き換え

元々このアプリはReact SPAで作成していました。理由としては、今の席替えアプリよりも機能が少なく、ページを遷移しないアプリケーションとして簡易的なものを考えていたからです。しかし、機能が増えると、ページを遷移させる機会が増えることが予想され、Next.jsに移行するという流れになりました。

苦労した点

既存のコードがある状態でNext.jsを並行して学習していたため、当初は工数を少なく見積もっていました。しかし、実際に書き始めると、ページを跨いで状態を管理することやページごとにどう認証を管理するのか、過去の自分が書いたコンポーネント設計が粗く、移行の際に大幅な書き直しが必要になりました。

どう乗り越えたか

Next.jsの公式のドキュメントとReactの公式のドキュメントを開き、違いを意識しながらコードを書いていきました。Next.jsで書き始めると、ReactはあくまでUIのためのライブラリであり、Next.jsはファイルルーティングやサーバーコンポーネントなど、React SPAでライブラリを入れないとできなかったことが初めから備わっていることが実感できました。

また、ページを跨いだ状態管理については、アプリ全体で共有したい状態をグローバルステートに移行し、コンポーネント内に留めておく状態との切り分けについて考えることができました。

席替えロジックの実装

苦労した点

席替えを実装する際に、ランダムな入れ替えをコードで表現する方法から苦労しました。またそこから設定した配慮をもとに、席替えを評価し、一番良い座席を出力するにはどうするべきなのかということも難しかったです。

どう乗り越えたか

正直に言うと、AIにたくさん聞きました。しかし、出力したコードだけでなく、出てきたワード(ここで言うとフィッシャーイェーツ)などは必ずブラウザで検索し、必ず根拠を持つようにしました。分からないことは時間をかけて理解をしていくということを常に意識していました。

実装までの流れとして、

分からないことはAIに聞く

気になったワードはブラウザで検索し、根拠を持つ

公式のドキュメントを必ず見に行って、自分なりの言葉で説明できるようになる

苦労したことはQiitaの記事にまとめ、言語化する

このアプリ全体にも言えるのですが、上記の流れは自分の学習のスタイルでもあります。AIに聞いたことは自分が責任を持って説明できるようにしています。

以下に記事も載せていますのでご覧下さい。

認証の状態をどう管理するのか

苦労したこと

公式のドキュメントを見ても、用語の意味が分からず理解に苦しみました。特にクライアントとサーバー間のやり取りにおける用語「JWT、セッション、Cookie」が聞いたことあるけど、書いてある文章が読み解けないことが多くありました。

どう乗り越えたか

ひとつひとつの単語を調べて、意味を繋いでいくようにして理解に努めました。公式ドキュメントを読み進めることでコードは書けましたが、背景にある仕組みの理解はまだ発展途上です。

現在、サーバー周りの知識が不足しているのもあり「ネットワークはなぜつながるのか」という本を読んでさらに知識をつけようとしています。この本を読みながら、以前は意味がつかめなかった用語が少しずつ繋がっていく感覚があり、学習の手応えを感じています。

終わりに

アプリを実際に使ってもらった反応

実際に教員の方々に使ってもらったところ、以下のような反応をもらいました。

・動いているだけでも感動なのに、席替えに必要な機能がたくさんあってこれからも使っていきたい!
・ユーザーの登録をしないでも、アプリを操作できるのが楽しい!
・座席を印刷するときに、生徒視点の教室配置になっているので、教師視点の教室配置も印刷できると、使いやすくなりそう!
・隣同士だけでなく、班の人も考えて席替えをしているから班の人たちのことも配慮に入れられるともっと良くなりそう!

アプリの今後の展望

これからも教員の方々に使っていただけるように、私のペースになりますが、今後もアップデートを検討しています。

追加したい機能

・班の中での相性も配慮して席替えができる機能
・教室の中で席替えをしても固定の席になる指定席機能
・過去の座席配置と比較できる機能
・印刷時に教師視点の教室配置で印刷できる機能

まとめ

今回作成した席替えアプリはお金をいただく実務ではなかったのですが、「使ってもらう人の役に立ちたい」「使ってもらう人の気持ちを考えて作りたい」という気持ちが常にありました。

自分がプログラミング学習を始めた当初、よく「実務が一番成長するから!」とたくさんの人に言われたことを今になって思い出します。これはきっと責任感だけでなく、相手を思いやる気持ちから自分の現状の実力以上にいろんなことを調べて、学び、それが成長につながるのではないかと今になって実感しています。

難しいことだらけでしたが、それ以上にとても充実した時間でした。早くソフトウェアエンジニアとして転職し、もっとたくさんの人の問題を解決していきたいと思いました。

2026年11月の転職を目標に、引き続き学習を続けていきます。
Xで毎日続けている学習記録の投稿はもちろん、今度はバックエンドの言語であるPHPにも挑戦したいと思っています。

これからも現場の課題を技術で解決できるエンジニアを目指していきます。
できるまでやればできる!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?