Міграція сайту з PHP-фреймворка на React/Next.js
Міграція з Laravel/Symfony/CodeIgniter на Next.js — це зміна не тільки технології, але й підходу до рендерингу, розгортання та розподілу відповідальності. Backend залишається (або трансформується на API), фронтенд переписується.
Стратегії міграції
Strangler Fig — поступова заміна: Next.js захоплює маршрути один за одним, поки PHP повністю не витісниться. Nginx/CDN спрямовує трафік на правильний сервер за URL-шаблонами.
Big Bang — повна заміна за одне розгортання. Швидше для малих сайтів, рискованіше для великих.
Гібрид — Next.js як фронтенд, PHP як API (Laravel API). Зберігає backend-інвестиції.
Підхід Strangler Fig через Nginx
server {
server_name mysite.com;
# Нові маршрути → Next.js
location /blog/ {
proxy_pass http://nextjs:3000;
}
location /about {
proxy_pass http://nextjs:3000;
}
# Старі маршрути → Laravel
location / {
proxy_pass http://laravel:8000;
}
}
Міграція бізнес-логіки
PHP/Laravel валідація → Zod:
// До (Laravel)
// $request->validate([
// 'email' => 'required|email|unique:users',
// 'name' => 'required|min:2|max:255',
// ]);
// Після (Zod + React Hook Form)
const schema = z.object({
email: z.string().email('Неправильна електронна пошта'),
name: z.string().min(2).max(255),
});
PHP ORM Eloquent → Prisma або шар API:
// До (Eloquent у Laravel)
// User::where('active', true)->with('posts')->paginate(10);
// Після (Prisma у Next.js API Route)
const users = await prisma.user.findMany({
where: { active: true },
include: { posts: true },
take: 10,
skip: (page - 1) * 10,
});
Laravel API + Next.js фронтенд
Замість повного переписування backend — перетворюємо Laravel на API:
// Laravel: перетворюємо на Sanctum API
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('posts', PostController::class);
Route::apiResource('products', ProductController::class);
});
// ProductController.php
public function index(Request $request): JsonResponse
{
$products = Product::query()
->when($request->category, fn($q) => $q->whereHas('category', fn($q) => $q->where('slug', $request->category)))
->paginate($request->per_page ?? 12);
return ProductResource::collection($products)->response();
}
// Next.js: клієнт до Laravel API
import createClient from 'openapi-fetch';
import type { paths } from '@/types/api'; // автогенеровані з OpenAPI
const client = createClient<paths>({ baseUrl: process.env.LARAVEL_API_URL });
const { data, error } = await client.GET('/api/products', {
params: { query: { category: 'electronics', per_page: 12 } },
});
Міграція шаблонів Blade/Twig → React
{{-- Blade --}}
@foreach($products as $product)
<div class="product-card">
<img src="{{ $product->image_url }}" alt="{{ $product->name }}">
<h3>{{ $product->name }}</h3>
<span>{{ number_format($product->price) }} руб.</span>
<a href="{{ route('products.show', $product->slug) }}">Детальніше</a>
</div>
@endforeach
// React еквівалент
const ProductCard = ({ product }: { product: Product }) => (
<div className="product-card">
<img src={product.imageUrl} alt={product.name} />
<h3>{product.name}</h3>
<span>{product.price.toLocaleString('uk-UA')} руб.</span>
<Link href={`/products/${product.slug}`}>Детальніше</Link>
</div>
);
Аутентифікація: PHP sessions → JWT/Cookies
// Next.js: аутентифікація через Laravel Sanctum
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
export const { auth, handlers } = NextAuth({
providers: [
Credentials({
async authorize(credentials) {
const res = await fetch(`${process.env.LARAVEL_URL}/api/auth/login`, {
method: 'POST',
body: JSON.stringify(credentials),
headers: { 'Content-Type': 'application/json' },
});
if (!res.ok) return null;
return res.json();
},
}),
],
});
Часова шкала міграції
| Тип сайту | Strangler Fig | Big Bang |
|---|---|---|
| Корпоративний (10–20 сторінок) | 4–8 тижнів | 3–6 тижнів |
| Блог/портал (50–200 сторінок) | 8–16 тижнів | 6–12 тижнів |
| Інтернет-магазин | 3–6 місяців | 2–4 місяці |
| Складний портал | 6–12 місяців | Недоцільно |







