$ ls ./menu

© 2025 ESSA MAMDANI

cd ../blog
5 min read
Web Development

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

> Learn how to transform your Laravel monolith into a high-performance headless architecture using Next.js. This guide covers everything from API refactoring and Sanctum authentication to SEO-friendly data fetching.

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

Let’s be honest: Laravel is a masterpiece. For years, it has been my go-to for building robust, battle-tested applications. But as the web evolves, the demand for ultra-snappy, highly interactive, and SEO-optimized user experiences has pushed us toward the "Headless" paradigm.

Migrating from a Laravel Blade-based monolith to a Next.js frontend isn't just a trend; it’s a strategic move to decouple your business logic from your presentation layer. In this guide, I’m going to walk you through the architectural shift, the pitfalls to avoid, and the "pro-level" implementation details you won't find in basic documentation.


1. The Architectural Shift: Embracing Headless Laravel

When you migrate to Next.js, you aren't "leaving" Laravel. You are promoting Laravel to its most powerful role: a robust, stateless JSON API.

In a traditional Blade setup, Laravel handles routing, session management, and HTML rendering. In the Next.js world:

  • Laravel handles the database, business logic, authentication, and background jobs.
  • Next.js handles the UI, client-side routing, and Server-Side Rendering (SSR).

Pro Tip: The "Strangler Fig" Pattern

Don't try to rewrite your entire app in one weekend. Use the Strangler Fig pattern. Migrate one module at a time (e.g., the blog or the dashboard) and use a reverse proxy like Nginx to route traffic between the old Blade views and the new Next.js app.


2. Preparing the Backend: Laravel as an API

Before touching a single line of React code, your Laravel backend needs to be "API-first."

Step 1: Install Laravel Sanctum

Sanctum is the gold standard for authenticating SPAs or decoupled frontends. It provides a featherweight authentication system for SPAs that need to maintain session cookies.

bash
1composer require laravel/sanctum
2php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
3php artisan migrate

Step 2: API Resources

Stop returning raw Eloquent models. It creates tight coupling between your database schema and your frontend. Use API Resources to transform your data.

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        'joined_at' => $this->created_at->format('Y-m-d'),
9    ];
10}

3. Setting Up the Next.js Frontend

With Next.js 14+ and the App Router, we have a powerful toolkit for handling data fetching and SEO.

Initialize the Project

bash
1npx create-next-app@latest my-frontend --typescript --tailwind --eslint

Configure Environment Variables

Create a .env.local file to point to your Laravel API.

env
1NEXT_PUBLIC_BACKEND_URL=http://localhost:8000

4. Authentication: The Bridge Between Frameworks

This is where most developers stumble. Since Next.js and Laravel run on different ports (or domains), you must handle CORS and Cookies correctly.

The Laravel Side

In config/cors.php, ensure supports_credentials is set to true, and your allowed_origins includes your Next.js URL.

The Next.js Side

I highly recommend using Laravel Breeze (Next.js Edition) as a reference. It uses a custom hook approach to manage authentication state. Here is a simplified version of what a login request looks like using axios:

typescript
1import axios from 'axios';
2
3const api = axios.create({
4    baseURL: process.env.NEXT_PUBLIC_BACKEND_URL,
5    withCredentials: true, // Crucial for Sanctum cookies
6});
7
8export const login = async (credentials) => {
9    // 1. Initialize CSRF protection
10    await api.get('/sanctum/csrf-cookie');
11    
12    // 2. Perform login
13    const response = await api.post('/login', credentials);
14    return response.data;
15};

5. Data Fetching: Server Components vs. Client Components

One of the biggest advantages of Next.js over a standard React SPA is Server Components.

Server-Side Fetching (SEO & Speed)

For public pages like blog posts or landing pages, fetch data directly on the server. This eliminates the "loading spinner" experience.

tsx
1// app/posts/page.tsx
2async function getPosts() {
3  const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/posts`, {
4    next: { revalidate: 60 }, // Cache for 60 seconds
5  });
6  return res.json();
7}
8
9export default async function Page() {
10  const posts = await getPosts();
11  
12  return (
13    <main>
14      <h1>Latest Insights</h1>
15      {posts.map(post => (
16        <div key={post.id}>{post.title}</div>
17      ))}
18    </main>
19  );
20}

Client-Side Fetching (Dashboard & Interactivity)

For authenticated dashboards where SEO doesn't matter, use SWR or TanStack Query. These libraries handle caching, revalidation, and "stale-while-revalidate" logic beautifully.


6. Common Pitfalls and How to Avoid Them

The "Trailing Slash" Nightmare

Laravel often redirects URLs with trailing slashes, which can break Next.js API calls. Ensure your NEXT_PUBLIC_BACKEND_URL does not end with a slash, and be consistent in your Laravel routes.

Image Optimization

Laravel developers are used to asset('path/to/img.jpg'). In Next.js, use the <Image /> component. If your images are hosted on the Laravel server, you must whitelist the domain in next.config.js:

javascript
1module.exports = {
2  images: {
3    domains: ['api.yourdomain.com'],
4  },
5}

TypeScript is Non-Negotiable

If you’re moving away from the "magic" of PHP, don't trade it for the "chaos" of plain JavaScript. Use TypeScript to define interfaces for your API responses. This creates a contract between your backend and frontend that saves hours of debugging.


7. Deployment Strategy

When you go live, you have two main paths:

  1. Vercel (Frontend) + Laravel Forge (Backend): This is the "elite" setup. Vercel handles the global distribution of your Next.js app, while Forge manages your PHP environment on DigitalOcean or AWS.
  2. Monorepo on a VPS: You can run both on a single server using Docker Compose and an Nginx reverse proxy. This is more cost-effective for smaller projects but requires more DevOps knowledge.

Final Thoughts

Migrating from Laravel to Next.js is a significant architectural decision. It increases complexity—you now have two applications to maintain instead of one—but the trade-off in user experience, performance, and developer ecosystem is worth it.

By keeping Laravel as your powerful data engine and Next.js as your high-performance UI layer, you get the best of both worlds.

If you're stuck on a specific part of the migration—be it middleware, state management, or deployment—drop a comment or reach out. I’ve navigated these waters many times, and the view from the other side is spectacular.

Happy coding.