$ ls ./menu

© 2025 ESSA MAMDANI

cd ../blog
5 min read
Web Development

From Monolith to Modern: The Definitive Guide to Migrating Laravel to Next.js

> Transition your Laravel monolith to a high-performance, decoupled architecture using Next.js. This guide covers API refactoring, authentication bridges, and the "Strangler Fig" migration strategy for elite developers.

Audio version coming soon
From Monolith to Modern: The Definitive Guide to Migrating Laravel to Next.js
Verified by Essa Mamdani

Laravel has been the backbone of the web for over a decade. Its "batteries-included" philosophy is unmatched for rapid development. But as the web evolves toward the edge, and user expectations for "app-like" responsiveness skyrocket, many of us are finding that the traditional Blade-based monolith is hitting a ceiling.

I’ve guided several high-traffic platforms through this exact transition. Moving from Laravel to Next.js isn't just about changing your view layer; it’s a fundamental shift in how you think about state, data fetching, and the boundary between server and client.

In this guide, I’m going to show you how to execute this migration with precision, ensuring zero downtime and a massive upgrade in performance.


1. The Philosophical Shift: Why Next.js?

Before we touch code, let's address the why. Laravel is fantastic at backend logic, but Next.js offers things Blade simply can't:

  • React Server Components (RSC): The ability to fetch data on the server and stream it to the client.
  • Edge Rendering: Deploying your frontend globally via Vercel or Cloudflare, reducing TTFB to milliseconds.
  • Superior DX: Fast Refresh, a massive ecosystem of UI libraries (Radix, Shadcn/ui), and a type-safe bridge between your frontend and backend.

We aren't throwing Laravel away. We are evolving it into a Headless API.


2. The Strategy: The Strangler Fig Pattern

Don't do a "Big Bang" rewrite. You will fail. Instead, use the Strangler Fig Pattern.

The goal is to keep your Laravel app running while you slowly "strangle" its features, replacing them one by one with Next.js routes. You can use a reverse proxy (like Nginx or a service like Cloudflare Workers) to route specific paths to Next.js while keeping the rest on Laravel.


3. Phase 1: Refactoring Laravel into a Robust API

Your first task is to transform your controllers. Blade views must go; JSON is your new language.

API Resources are Non-Negotiable

Stop returning raw arrays or models. Use Laravel’s JsonResource to ensure your API contracts remain stable even if your database schema changes.

php
1// app/Http/Resources/UserResource.php
2public function toArray($request)
3{
4    return [
5        'id' => $this->id,
6        'name' => $this->name,
7        'email' => $this->email,
8        'initials' => strtoupper(substr($this->name, 0, 2)), // Business logic stays in Laravel
9    ];
10}

Pro Tip: Laravel Sanctum for SPA Auth

If your Next.js app is on a subdomain (e.g., app.essamamdani.com) and your API is on another (api.essamamdani.com), use Laravel Sanctum. It provides a featherweight authentication system for SPAs that uses cookie-based session authentication. This avoids the headaches of JWT management and manual token refreshing.


4. Phase 2: Setting up the Next.js Architecture

With the App Router (introduced in Next.js 13), we have a structure that mirrors Laravel’s routing power but with the reactivity of React.

Environment Configuration

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

bash
1NEXT_PUBLIC_API_URL=https://api.myapp.test

Data Fetching with Type Safety

In Next.js, we leverage Server Components to fetch data directly. This eliminates the "loading spinner hell" common in traditional SPAs.

tsx
1// app/dashboard/page.tsx
2import { cookies } from 'next/headers';
3
4async function getProfile() {
5  const cookieStore = cookies();
6  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/user`, {
7    headers: {
8      'Cookie': cookieStore.toString(), // Pass cookies for Sanctum auth
9      'Accept': 'application/json',
10    },
11  });
12
13  if (!res.ok) throw new Error('Failed to fetch data');
14  return res.json();
15}
16
17export default async function Dashboard() {
18  const user = await getProfile();
19
20  return (
21    <main>
22      <h1>Welcome back, {user.data.name}</h1>
23    </main>
24  );
25}

5. Phase 3: Bridging the Authentication Gap

The hardest part of migrating from a monolith is handling the session. Laravel expects a CSRF token and a session cookie.

The Strategy:

  1. CSRF Initializer: Create a utility in Next.js that hits the /sanctum/csrf-cookie endpoint before any login attempt.
  2. Axios/Fetch Interceptors: Ensure withCredentials: true is set on every request to pass those cookies back and forth.

Pro Tip: If you are moving to a fully decoupled architecture where the frontend and backend are on completely different domains (not subdomains), you’ll need to switch to Sanctum Token Authentication or OAuth2 (Laravel Passport). I generally recommend staying on subdomains to keep the security benefits of HttpOnly cookies.


6. Handling State: Moving from Alpine.js/Livewire to React

If you were using Livewire, you’ll find that Next.js handles "state" differently. In Livewire, state is often synced with the server automatically. In Next.js, you have a choice:

  1. Server State: Use fetch in Server Components. For mutations (POST/PUT), use Server Actions.
  2. Client State: Use Zustand or React Context for UI state (sidebars, modals).
  3. Caching: Use TanStack Query (React Query) if you need advanced client-side caching and synchronization.

Example: A Server Action for Updates

tsx
1// app/actions.ts
2'use server'
3
4import { revalidatePath } from 'next/cache';
5
6export async function updateProfile(formData: FormData) {
7  const response = await fetch('https://api.myapp.test/api/user', {
8    method: 'POST',
9    body: formData,
10    // Add auth headers...
11  });
12
13  if (response.ok) {
14    revalidatePath('/dashboard'); // Instant UI update
15  }
16}

7. SEO and Metadata

One of Laravel's strengths is how easy it is to inject SEO tags via Blade. In Next.js, you use the Metadata API. This is significantly more powerful because it allows for dynamic, nested metadata that is fully SSR-compatible.

tsx
1// app/posts/[slug]/page.tsx
2export async function generateMetadata({ params }) {
3  const post = await getPost(params.slug);
4  return {
5    title: post.title,
6    description: post.excerpt,
7    openGraph: {
8      images: [post.cover_image],
9    },
10  };
11}

8. Deployment and CI/CD

When you're elite, you don't just "FTP" files. Your Laravel API should stay on a robust VPS or specialized PHP host (like Laravel Forge), while your Next.js app should live on Vercel or AWS Amplify.

Insider Knowledge: Set up a Monorepo using Turborepo. Keep your /backend (Laravel) and /frontend (Next.js) in one repository. This allows you to share types (via tools like zod-to-php or simple JSON schemas) and run your entire stack with a single command.


Final Thoughts

The migration from Laravel to Next.js is a journey from a "Page-based" mindset to a "Component-based" mindset. You aren't losing Laravel’s power; you are refocusing it. Laravel becomes your high-performance engine—handling migrations, queues, and business logic—while Next.js becomes your high-performance cockpit.

The result? A DX that makes your team faster and a UX that keeps your users coming back.

Keep building.