$ ls ./menu

© 2025 ESSA MAMDANI

cd ../blog
6 min read
Web Development

From Monolith to Modernity: The Architect’s Guide to Migrating Laravel to Next.js

> Transitioning from a Laravel monolith to a decoupled Next.js architecture requires more than just a syntax change; it demands a shift in architectural philosophy. This guide covers the end-to-end process of turning Laravel into a robust API and leveraging Next.js for a world-class frontend.

Audio version coming soon
From Monolith to Modernity: The Architect’s Guide to Migrating Laravel to Next.js
Verified by Essa Mamdani

The "Modern Monolith" is a beautiful thing. If you’ve been building with Laravel and Blade (or even Inertia.js), you know the speed of development is unmatched. But as your application scales, your requirements for SEO, sub-millisecond perceived performance, and a richer ecosystem of UI components often lead you to one inevitable conclusion: It’s time to move the frontend to Next.js.

I’ve overseen dozens of migrations where we took a heavy-duty Laravel backend and decoupled it to serve a Next.js frontend. It’s not just a trend; it’s a strategic move to leverage React’s Server Components (RSC) and Vercel’s global edge network.

In this guide, I’m going to skip the "Hello World" basics. We’re going deep into the architectural shift, the authentication hurdles, and how to maintain the "Laravel magic" while living in the JavaScript ecosystem.


The Strategy: Evolution, Not Revolution

The biggest mistake I see developers make is trying to rewrite the entire stack at once. Unless you have an infinite budget and zero users, you should adopt the Headless Laravel approach.

Keep your migrations, models, service classes, and business logic in Laravel. Your goal is to transform your Laravel application into a high-performance JSON API, allowing Next.js to handle the presentation layer, routing, and state.

Why Next.js?

  • Server Components (RSC): Fetching data on the server reduces the JavaScript bundle sent to the client.
  • Image Optimization: Automatic resizing and WebP conversion out of the box.
  • Middleware: Edge-side logic that Laravel’s PHP middleware can’t touch in terms of latency.
  • Ecosystem: Access to libraries like Radix UI, Framer Motion, and TanStack Query.

Step 1: Preparing the Laravel Backend

Before you even run npx create-next-app, your Laravel backend needs to be ready to serve as a headless CMS/API.

1.1 Laravel Sanctum for Authentication

In a monolith, you use session-based auth. In a decoupled setup, you have two choices: Sanctum (Stateful/Cookie-based) or Passport (OAuth2/JWT). For most 1st-party Next.js apps, Sanctum’s SPA authentication is the gold standard because it’s more secure than storing JWTs in localStorage.

Configure your cors.php to allow your Next.js domain and ensure supports_credentials is set to true.

1.2 API Resources

Stop returning raw models. Use Laravel API Resources to transform your data. This ensures your Next.js frontend receives a consistent schema even if your database structure changes.

php
1// app/Http/Resources/UserResource.php
2public function toArray(Request $request): array
3{
4    return [
5        'id' => $this->id,
6        'name' => $this->name,
7        'email' => $this->email,
8        'avatar' => $this->profile_photo_url,
9        'joined_at' => $this->created_at->toIso8601String(),
10    ];
11}

Pro Tip: Use a tool like spatie/laravel-typescript-transformer to automatically generate TypeScript interfaces from your Laravel models and resources. This bridges the type-safety gap between your backend and frontend.


Step 2: Setting up the Next.js App Router

We are building for the future, so we use the Next.js App Router. Create your project with TypeScript and Tailwind CSS—don't even think about skipping these.

2.1 The Environmental Setup

Create a .env.local in your Next.js project:

env
1NEXT_PUBLIC_BACKEND_URL=http://localhost:8000
2NEXT_BACKEND_API_URL=http://laravel.test/api

2.2 Shared Fetch Logic

I recommend creating a robust fetch wrapper that handles CSRF protection and base URLs.

typescript
1// lib/api.ts
2const backendUrl = process.env.NEXT_BACKEND_API_URL;
3
4export async function apiFetch(endpoint: string, options: RequestInit = {}) {
5    const res = await fetch(`${backendUrl}${endpoint}`, {
6        ...options,
7        headers: {
8            'Accept': 'application/json',
9            'Content-Type': 'application/json',
10            ...options.headers,
11        },
12    });
13
14    if (!res.ok) {
15        // Handle 401s, 422s, etc.
16        throw new Error(await res.text());
17    }
18
19    return res.json();
20}

Step 3: Data Fetching and Server Components

This is where the magic happens. In Laravel, you’d pass data to a Blade view via a Controller. In Next.js, you fetch that data directly inside a Server Component.

The Laravel Controller

php
1public function index()
2{
3    $posts = Post::published()->latest()->paginate(10);
4    return PostResource::collection($posts);
5}

The Next.js Page

tsx
1// app/blog/page.tsx
2import { apiFetch } from '@/lib/api';
3
4async function getPosts() {
5    return await apiFetch('/posts');
6}
7
8export default async function BlogPage() {
9    const posts = await getPosts();
10
11    return (
12        <main className="max-w-4xl mx-auto py-12">
13            <h1 className="text-3xl font-bold">Latest Posts</h1>
14            <div className="mt-6 grid gap-6">
15                {posts.data.map((post: any) => (
16                    <article key={post.id} className="p-4 border rounded-lg">
17                        <h2 className="text-xl font-semibold">{post.title}</h2>
18                        <p className="text-gray-600">{post.excerpt}</p>
19                    </article>
20                ))}
21            </div>
22        </main>
23    );
24}

Pro Tip: Use revalidatePath or revalidateTag in Next.js to clear the cache when you update data in Laravel via a webhook. This gives you the speed of static sites with the freshness of a dynamic app.


Step 4: Solving the Authentication Puzzle

The hardest part of migrating from Laravel to Next.js is handling the session. If you use Sanctum, you need to ensure the Next.js server handles cookies correctly during SSR (Server-Side Rendering).

I highly recommend using NextAuth.js (now Auth.js) with a custom Credentials Provider that hits your Laravel /login endpoint. This allows you to manage the session within the Next.js ecosystem while letting Laravel handle the actual credential verification and database lookups.

Handling CSRF

Laravel requires a CSRF cookie for stateful requests. Your Next.js app must hit the /sanctum/csrf-cookie endpoint before making a login request.


Step 5: Handling Forms and Mutations

In Laravel, you’re used to @csrf and method="POST". In Next.js, we use Server Actions.

tsx
1// app/contact/action.ts
2'use server'
3
4export async function submitForm(formData: FormData) {
5    const rawFormData = {
6        name: formData.get('name'),
7        message: formData.get('message'),
8    };
9
10    const response = await fetch(`${process.env.NEXT_BACKEND_API_URL}/contact`, {
11        method: 'POST',
12        body: JSON.stringify(rawFormData),
13        headers: { 'Content-Type': 'application/json' }
14    });
15
16    // Handle response and redirect
17}

This keeps your logic on the server, hiding your API endpoints from the client-side bundle and preventing CORS issues.


Advanced Architecture: The "BFF" Pattern

For larger migrations, consider the Backend-for-Frontend (BFF) pattern. Instead of the browser talking directly to Laravel, it talks to the Next.js API routes (or Server Actions). Next.js then communicates with Laravel.

Why?

  1. Security: You can hide your Laravel API behind a VPC.
  2. Data Shaping: You can aggregate multiple Laravel API calls into a single response for the frontend.
  3. Caching: You can implement Redis caching at the Next.js layer to reduce the load on your PHP workers.

Deployment: The Final Frontier

When you migrate, your deployment strategy changes.

  • Laravel: Deploy to a VPS (Forge/DigitalOcean) or serverless (Vapor).
  • Next.js: Vercel is the gold standard for a reason. The integration with the App Router and Edge Functions is unbeatable.

If you must self-host Next.js, use a Dockerized setup on Coolify or a standard Node.js server with PM2. Just remember that you’ll lose some of the automated image optimization features unless you configure a separate provider.


Conclusion: Is it worth it?

Migrating from a Laravel monolith to Next.js is a significant investment. If you’re building a simple CRUD app, a monolith is likely faster. But if you are building a high-traffic platform that requires a superior user experience, SEO dominance, and a scalable frontend team, this migration is the best move you can make.

You aren't leaving Laravel; you're promoting it. It's no longer just a web framework; it's the powerful, reliable engine driving your modern, React-powered frontend.

Stay technical, stay elite. See you in the next one.