はじめに
普段はLaravelで受託開発をしている。PHP歴は5年くらい。
最近、個人開発でNext.jsを触り始めた。フロントエンドはjQuery止まりだったので、React系は初めて。
数週間触ってみた所感をまとめる。Laravel経験者の視点で書くので、同じような境遇の人の参考になれば。
なぜNext.jsを選んだか
正直、最初は「Reactを勉強しよう」くらいの気持ちだった。
調べていくうちに、Reactだけだとルーティングやビルド周りを自分で設定する必要があると知った。面倒くさい。LaravelみたいにRails的な「これに乗っかればOK」というフレームワークが欲しかった。
Next.jsがそれに近いらしい、ということで選んだ。
環境構築
npx create-next-app@latest my-app
これだけで動く環境ができる。
Laravelも composer create-project で一発だけど、それと同じ感覚。TypeScript、ESLint、Tailwind CSSあたりも初期設定で選べる。楽。
ディレクトリ構成
Next.js(App Router)の構成は最初戸惑った。
app/
├── page.tsx # ルート(/)
├── about/
│ └── page.tsx # /about
└── blog/
└── [id]/
└── page.tsx # /blog/:id
Laravelでいう routes/web.php でルーティングを定義する感覚とは違う。ディレクトリ構成がそのままURLになる。
最初は「明示的にルート定義したい」と思ったけど、慣れると楽。ファイルを作ればルートができる。
コンポーネント指向
ここが一番の違いだった。
Laravelの場合、Bladeテンプレートでビューを書く。ロジックはコントローラー、表示はBlade、と分かれている。
// コントローラー
public function show($id)
{
$post = Post::find($id);
return view('posts.show', compact('post'));
}
<!-- Blade -->
<h1>{{ $post->title }}</h1>
<p>{{ $post->body }}</p>
Next.js(React)は、ロジックと表示が一体になる。
// page.tsx
export default async function PostPage({ params }: { params: { id: string } }) {
const post = await getPost(params.id);
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}
最初は違和感があった。「ロジックとビューは分けるべきでは?」と。
でも触っていくうちに、コンポーネント単位で完結している方がわかりやすい場面も多いと感じた。特に、UIの状態管理が絡むとき。
Server Components と Client Components
Next.js 13以降のApp Routerでは、コンポーネントがデフォルトでサーバーサイドで実行される(Server Components)。
クライアントで動かしたい場合は 'use client' をつける。
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
LaravelでいうBladeの @livewire に近い感覚……かもしれない。サーバーとクライアントの境界を意識する必要がある。
ここは正直まだ完全に理解しきれていない。とりあえず「状態管理やイベントハンドラが必要ならClient」くらいの理解で進めている。
データ取得
Laravelでは、コントローラーでEloquentを叩いてビューに渡す。
Next.jsでは、Server Componentsの中で直接fetchできる。
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
コンポーネントの中でawaitできるのは新鮮だった。Laravelの感覚だと「コントローラーで取得してビューに渡す」だけど、それがなくなる。
スタイリング
Tailwind CSSを使っている。
<button className="bg-blue-500 text-white px-4 py-2 rounded">
送信
</button>
最初は「クラス名長すぎでは?」と思ったけど、慣れると楽。CSSファイルを別で管理しなくていい。
LaravelでもTailwindは使えるので、ここは違和感なかった。
つまずいたところ
1. useStateが使えない
Server Componentsでは useState や useEffect が使えない。
// これはエラー
import { useState } from 'react';
export default function Page() {
const [count, setCount] = useState(0); // ← Server Componentでは使えない
// ...
}
'use client' をつけるか、Client Componentとして分離する必要がある。最初これがわからなくてハマった。
2. 型定義
TypeScriptに慣れていなかったので、型エラーに苦しんだ。
// propsの型を定義する必要がある
type Props = {
title: string;
body: string;
};
export default function Post({ title, body }: Props) {
// ...
}
PHPにも型宣言はあるけど、TypeScriptほど厳密ではない。最初は面倒だったけど、エディタの補完が効くようになるとありがたさがわかった。
3. ビルドエラーが本番で出る
開発環境では動くのに、next build でエラーになることがあった。
Server Componentsでブラウザ専用のAPIを使っていたり、型エラーがあったり。開発中はエラーにならないのに、ビルド時に怒られる。
こまめにビルドして確認する癖をつけた。
Laravel経験が活きたところ
- MVC的な考え方: コンポーネント指向とはいえ、データ取得と表示を分ける意識は役立った
- Tailwind CSS: Laravelでも使っていたのでそのまま使えた
-
環境変数:
.envでの管理は同じ感覚 - ルーティングの概念: 書き方は違うけど、URLとコードを紐づける発想は同じ
今後
まだ触り始めたばかりなので、わかっていないことが多い。
- 認証周り(NextAuth.js)
- データベース連携(Prisma)
- デプロイ最適化
このあたりを勉強していきたい。
Laravelを捨てるつもりはないけど、選択肢が増えるのはいいことだと思う。案件の内容によって使い分けられるようになりたい。
おわりに
Laravel畑からNext.jsに来てみた所感をまとめた。
正直、最初は戸惑うことが多かった。でも、数週間触ってみて「これはこれで合理的だな」と思う場面が増えてきた。
同じようにPHP/Laravel出身でフロントエンドに手を出そうとしている人の参考になれば嬉しい。