本投稿は TECOTEC Advent Calendar 2025 の10日目の記事です。
こんにちは。システム開発第二事業部の杉原です。 普段はSPA(シングルページアプリケーション)のバックエンド開発を担当しています。
皆さんは、SPA という言葉をご存じでしょうか? 単一のページでコンテンツの切り替えのみを行うWebアプリケーションの実装手法、という説明を聞いたことがある方も多いかと思います。しかし「概念は知っているけど、実際に何をしているのか分からない」という方も少なくないでしょう。
今回は、SPAとMPAそれぞれの構成で作成したTodoアプリを比較しながら、SPAの特徴を整理したいと思います。
当記事の目標
今回の記事では
「概念は知っているけど、実際に何をしているのか分からない」 という方向けに、
- SPAとMPAの描画処理は具体的にどう違うのか
- SPAはMPAと比べてどこが優れているのか
上記二点を理解していただけることを目標とします!
比較に用いるアプリについて
今回は、
SPA
- FE: Vue 3 + TypeScript
- BE: Laravel
MPA
- FE: Blade
- BE: Laravel
という構成で、Todoアプリを作成、比較しました
実際の画面
それぞれの実装方法で作成したWebアプリです

タイトル以外同じ構成に見えますが…

読み込んでいるHTMLの構造は大きく異なります。
SPAとMPAの違い
ブラウザでは、
HTMLをDOM*1に変換 → DOMをもとに画面に表示
という流れで画面を作っています
MPAの場合
サーバーがHTMLを生成して返す
画面更新のたびにHTML全体を取得
ブラウザがDOMを再構築 → 画面全体の更新が必要になり、遷移や描画が重くなる
SPAの場合
最初の1回だけHTML(index.html)を読み込む
画面遷移の代わりにJS(Vue)がDOMを操作
データはAPI(JSON)で取得 → HTML全体の更新は発生せず、画面更新が軽くなる
実例:タスク追加の挙動
Todoアプリのタスクを追加することで、画面の更新のされ方を見てみます
MPA
タスクを追加すると…


2つのメソッドが実行されました! ①でデータベースが更新され、 ②でHTMLが返ってきます(②のレスポンス一部抜粋)
<ul>
<li>
<form action="http://127.0.0.1:8000/tasks/3" method="POST"
style="display:inline">
<input type="hidden" name="_token"
value="V2QYf2RF4XN7uUyYRRodPGlCxAKo68YTKHpsyHaS" autocomplete="off">
<input type="hidden" name="_method" value="PATCH">
<input type="checkbox" onchange="this.form.submit()" >
</form>
タスク1
<form action="http://127.0.0.1:8000/tasks/3" method="POST"
style="display:inline">
<input type="hidden" name="_token"
value="V2QYf2RF4XN7uUyYRRodPGlCxAKo68YTKHpsyHaS" autocomplete="off">
<input type="hidden" name="_method" value="DELETE">
<button>削除</button>
</form>
</li>
<li>
<form action="http://127.0.0.1:8000/tasks/4" method="POST"
style="display:inline">
<input type="hidden" name="_token"
value="V2QYf2RF4XN7uUyYRRodPGlCxAKo68YTKHpsyHaS" autocomplete="off">
<input type="hidden" name="_method" value="PATCH">
<input type="checkbox" onchange="this.form.submit()" >
</form>
タスク2
<form action="http://127.0.0.1:8000/tasks/4" method="POST"
style="display:inline">
<input type="hidden" name="_token"
value="V2QYf2RF4XN7uUyYRRodPGlCxAKo68YTKHpsyHaS" autocomplete="off">
<input type="hidden" name="_method" value="DELETE">
<button>削除</button>
</form>
</li>
<li>
<form action="http://127.0.0.1:8000/tasks/5" method="POST"
style="display:inline">
<input type="hidden" name="_token"
value="V2QYf2RF4XN7uUyYRRodPGlCxAKo68YTKHpsyHaS" autocomplete="off">
<input type="hidden" name="_method" value="PATCH">
<input type="checkbox" onchange="this.form.submit()" >
</form>
追加タスク
<form action="http://127.0.0.1:8000/tasks/5" method="POST"
style="display:inline">
<input type="hidden" name="_token"
value="V2QYf2RF4XN7uUyYRRodPGlCxAKo68YTKHpsyHaS" autocomplete="off">
<input type="hidden" name="_method" value="DELETE">
<button>削除</button>
</form>
</li>
</ul>
SPA
タスクを追加すると…


のみが実行されました!
レスポンスを確認すると
{
created_at: "2025-12-07T18:27:23.000000Z",
id: 20,
title: "追加タスク",
updated_at: "2025-12-07T18:27:23.000000Z",
}
追加したデータのみがJSON形式で返ってきます!
このデータをもとに、DOMの更新のみを行っているため、

実際のHTMLには変化がありません!
画面描画までの流れのまとめ
MPAの場合
- リクエストを Laravel が受け取る
- routes/web.php が リクエストに対応するコントローラーを探す
- コントローラーのメソッドが実行される
- コントローラーが実行結果をBladeに渡す
- BladeがHTMLをレンダリング
- HTMLをブラウザに渡す
SPAの場合
- リクエストを Vue が受け取る
- Vite dev server が index.html を返す
- index.html が Vue アプリ本体を読み込む(main.ts)
- Vue Router が現在の URL に応じて表示するコンポーネントを切り替える
- 表示に必要なデータは、 Laravel にリクエストする(/api/...)
- LaravelがAPIレスポンス( JSON )を返す
- Vue(JS) が JSON を画面に反映
結論
MPA … サーバー側がHTMLを作成 → ブラウザに返す
SPA … 初回に読み込んだHTMLをもとに、ブラウザ側のJSでDOMを操作
そのため、SPAでは画面更新が高速で軽量になります!
テコテックの採用活動について
テコテックでは新卒採用、中途採用共に積極的に募集をしています。
採用サイトにて会社の雰囲気や福利厚生、募集内容をご確認いただけます。
ご興味を持っていただけましたら是非ご覧ください。
tecotec.co.jp
*1:JavaScriptがHTMLを操作するために構築するオブジェクトのこと