Reactと合わせてよく見かける、Viteって何だろう。Next.jsとは何が違うんだろう。
そんなところで少し疑問に思ったので、さらっと学習してみました。
AIによって生成されたアーキテクチャやソースコードで意味が分からないままだと、あとで自分で直したいときに困ったことになります。
AIに改修を任せる場合でも、レビューは必要ですから、最低限の知識は入れておきたいですよね。
さて今回は、React + Viteで小さな問い合わせフォームを作りながら、React、Vite、Next.jsの役割を整理してみました。
まずReact、Vite、Next.jsの役割を整理
最初に混乱しやすいのは、React、Vite、Next.jsがどの部分を扱っているのかよく分からないこと。
実際には、担当している範囲が違います。
ざっくり分けると、次のようになります。
| 名前 | 役割 | この記事での見方 |
|---|---|---|
| React | UIを作るライブラリ | 画面をコンポーネントとして作る |
| Vite | 開発サーバーとビルドの道具 | Reactアプリを快適に開発し、公開用ファイルへまとめる |
| Next.js | Reactベースのフレームワーク | ルーティング、SSR / SSG、サーバー側処理なども含めてアプリを作る |
なるほど、Next.jsはReactを元に増強したフレームワークなのですね。一方でViteはビルドなどやりやすいようにしてくれる、開発支援ツールといったところでしょうか?
もちろんReactは、画面を部品として組み立てるためのライブラリです。コンポーネント、JSX、state、イベント処理といった考え方が中心になります。
Viteは、Reactアプリを開発するときの開発サーバーや、本番公開用のビルドを担当する道具です。ちなみに Vite は「ヴィート」と読みます。保存した変更を素早くブラウザに反映するHMR (ホット・モジュール・リプレイスメント)も、Viteのありがたさを感じやすい部分です。とはいえ、今時この手のツールは当たり前すぎて、あまり意識していなかったようです。
Next.jsは、Reactを土台にしたフレームワークです。ファイルベースのルーティング、SSR、SSG、Route Handlersなど、アプリ全体を作るための機能をまとめて持っています。SSR、つまりサーバーサイドレンダリングですが、ここら辺が Next.js の魅力といったところでしょうか。
さて今回は、学習用に小さなフォームアプリをReact + Viteで作ってみます。SPA(シングルページアプリケーション)ですが、ペラ1のめちゃ簡単なUIのみで、メールサーバーも何もないのであしからず。
React + Viteプロジェクトを作る
それではプロジェクトを作成して実装していきましょう。
Vite公式のGetting Startedでは、npm create vite@latest からプロジェクトを作れます。
Vite 8系ではNode.js 20.19+ または 22.12+ が必要です。古いNode.jsを使っている場合は、先に更新しておきます。
まずはNode.jsのバージョンを確認します。
node -v
次に、Reactテンプレートでプロジェクトを作ります。
npm create vite@latest react-vite-form-app -- --template react
cd react-vite-form-app
npm install
npm run dev
対話形式で進める場合は、次のように選びます。
- Project name:
react-vite-form-app - Framework:
React - Variant:
JavaScript
npm install は、package.json に書かれている依存パッケージをインストールするコマンドです。
npm run dev は、Viteの開発サーバーを起動するコマンドです。開発中はここでブラウザ表示を確認しながら、src/App.jsx や src/App.css を編集します。
全体の流れはこうです。
生成されるファイルを見る
ViteのReactテンプレートでは、最初からいくつかのファイルが作られます。
最初に見るなら、このあたりです。
| ファイル | 役割 |
|---|---|
package.json | 依存パッケージとnpm scriptsが書かれている |
index.html | ViteではプロジェクトルートにあるHTMLの入口 |
src/main.jsx | ReactアプリをHTMLの#rootへ差し込む入口 |
src/App.jsx | 画面本体のReactコンポーネント |
src/App.css | App.jsxに対応する見た目の調整 |
vite.config.js | Viteの設定ファイル |
最初から全部を理解しようとすると疲れます。
まずは、src/App.jsx を変えると画面の内容が変わり、src/App.css を変えると見た目が変わる、という感覚をつかむのがよさそうです。
src/App.jsxをフォームに置き換える
Viteテンプレートで作られた src/App.jsx を、次の内容に置き換えます。
import { useState } from 'react'
import './App.css'
const initialForm = {
name: '',
email: '',
message: '',
}
function validateForm(form) {
const nextErrors = {}
if (!form.name.trim()) {
nextErrors.name = '名前を入力してください'
}
if (!form.email.trim()) {
nextErrors.email = 'メールアドレスを入力してください'
} else if (!form.email.includes('@')) {
nextErrors.email = 'メールアドレスの形式を確認してください'
}
if (!form.message.trim()) {
nextErrors.message = '問い合わせ内容を入力してください'
}
return nextErrors
}
export default function App() {
const [form, setForm] = useState(initialForm)
const [errors, setErrors] = useState({})
const [submitted, setSubmitted] = useState(null)
function handleChange(event) {
const { name, value } = event.target
setForm((currentForm) => ({
...currentForm,
[name]: value,
}))
setErrors((currentErrors) => ({
...currentErrors,
[name]: '',
}))
setSubmitted(null)
}
function handleSubmit(event) {
event.preventDefault()
const nextErrors = validateForm(form)
setErrors(nextErrors)
if (Object.keys(nextErrors).length > 0) {
return
}
setSubmitted(form)
setForm(initialForm)
}
return (
<main className="app">
<section className="form-panel">
<p className="eyebrow">React + Vite</p>
<h1>問い合わせフォーム</h1>
<p className="lead">
入力値を state で管理し、送信時に簡単なチェックを行います。
</p>
<form onSubmit={handleSubmit} noValidate>
<label>
名前
<input
type="text"
name="name"
value={form.name}
onChange={handleChange}
aria-invalid={Boolean(errors.name)}
/>
</label>
{errors.name && <p className="error">{errors.name}</p>}
<label>
メールアドレス
<input
type="email"
name="email"
value={form.email}
onChange={handleChange}
aria-invalid={Boolean(errors.email)}
/>
</label>
{errors.email && <p className="error">{errors.email}</p>}
<label>
問い合わせ内容
<textarea
name="message"
value={form.message}
onChange={handleChange}
rows="5"
aria-invalid={Boolean(errors.message)}
/>
</label>
{errors.message && <p className="error">{errors.message}</p>}
<button type="submit">送信する</button>
</form>
</section>
{submitted && (
<section className="result-panel">
<h2>送信内容を受け付けました</h2>
<dl>
<div>
<dt>名前</dt>
<dd>{submitted.name}</dd>
</div>
<div>
<dt>メール</dt>
<dd>{submitted.email}</dd>
</div>
<div>
<dt>内容</dt>
<dd>{submitted.message}</dd>
</div>
</dl>
</section>
)}
</main>
)
}
ここで見たいのは、主に3つのstateです。
form: 入力中の値errors: バリデーションエラーsubmitted: 送信後に表示する内容
入力欄には、value={form.name} と onChange={handleChange} のような組み合わせがあります。これで、入力欄の表示とReactのstateがつながります。
handleChange では、event.target.name と event.target.value を使っています。各入力欄に name="email" のような属性を付けておくことで、どの入力欄が変更されても同じ関数で処理できます。
送信時は handleSubmit が動きます。event.preventDefault() は、ブラウザの通常のフォーム送信でページが再読み込みされるのを止めるためのものです。そのうえで validateForm を呼び、問題があれば errors を表示し、問題がなければ submitted に送信内容を入れます。
流れを図にすると、こうなります。
src/App.cssで見た目を整える
次に src/App.css を置き換えます。
#root {
width: 100%;
}
.app {
min-height: 100vh;
display: grid;
place-items: center;
gap: 24px;
padding: 40px 20px;
background: #edf2f7;
color: #1f2937;
}
.form-panel,
.result-panel {
width: min(100%, 640px);
padding: 28px;
border: 1px solid #d8dee8;
border-radius: 8px;
background: #ffffff;
}
.eyebrow {
margin: 0 0 8px;
color: #2563eb;
font-size: 0.85rem;
font-weight: 700;
}
h1,
h2,
p {
margin-top: 0;
}
.lead {
color: #4b5563;
}
form {
display: grid;
gap: 14px;
}
label {
display: grid;
gap: 6px;
font-weight: 700;
}
input,
textarea {
width: 100%;
box-sizing: border-box;
border: 1px solid #cbd5e1;
border-radius: 6px;
padding: 10px 12px;
font: inherit;
}
input:focus,
textarea:focus {
outline: 3px solid #bfdbfe;
border-color: #2563eb;
}
[aria-invalid='true'] {
border-color: #dc2626;
}
.error {
margin: -6px 0 0;
color: #dc2626;
font-size: 0.9rem;
}
button {
border: 0;
border-radius: 6px;
padding: 12px 16px;
background: #2563eb;
color: #ffffff;
font: inherit;
font-weight: 700;
cursor: pointer;
}
button:hover {
background: #1d4ed8;
}
.result-panel dl {
display: grid;
gap: 12px;
margin: 0;
}
.result-panel div {
display: grid;
gap: 4px;
}
.result-panel dt {
font-weight: 700;
}
.result-panel dd {
margin: 0;
color: #4b5563;
}
CSSは見た目の担当です。Reactの状態管理そのものは App.jsx にあります。
この分担も大事です。AIが生成したコードを見るときも、「画面の状態を変えているのはどこか」「見た目だけを変えているのはどこか」を分けて読むと、だいぶ追いやすくなります。
ビルドして静的ファイルにする
開発中は npm run dev で確認します。
公開用にまとめるときは、次のコマンドを使います。
npm run build
Viteでは、ビルド結果が dist/ に出力されます。
dist/
index.html
assets/
...
つまり、React + Viteの構成では、開発中はViteの開発サーバーで素早く確認し、公開時は dist/ にできた静的ファイルを配る、という流れになります。
npm run preview は、ビルド後の dist/ をローカルで確認するためのコマンドです。開発中の npm run dev とは役割が少し違います。
今回のフォーム画面は、開発中は次のように表示されます。ビルドすると、この画面を構成するHTML、CSS、JavaScriptが dist/ にまとめられます。

AIに任せるためにも、人間側が最低限見られるようにする
今回のフォーム作成で使ったのは、ReactやViteの機能のほんの一部でしょう。
それでも、一度も触ったことがない状態と、自分で小さく作って動かしてみた状態とでは、それだけでツールに対する印象が大きく変わります。
まだまだAIと人間の共同作業は続きそうですから、こちらもある程度はコードを理解してレビューする必要がありそうです。
一方で、どちらかというとフルスクラッチ好きでプログラミングをやってきた身としては、こういった学習コストが高くて手を出しづらかったことに無理やりでも触れる良い機会になって、結構楽しんでいたりします。 AIに作らせて終わりではなく、AIが出したコードから学ぶ、という使い方もできそうです。
来年にはまた事情が変わっていたらそれはそれで時代を引き受けるしかないですね。
まとめとして、ReactはUIを作るもの。Viteはその開発とビルドを支える。Next.jsはReactを使ったアプリ全体のフレームワークとして、より広い範囲を担当する。まずはその整理ができただけでも、次にWebアプリのコードを読むときのストレスは少し減るはずです。