$ ls ./menu

© 2025 ESSA MAMDANI

cd ../blog
5 min read
Web Development

From Blade to Brilliance: The Definitive Guide to Migrating Laravel to Next.js

> Transition your monolithic Laravel application to a high-performance, headless architecture using Next.js and React. This guide covers the architectural shifts, authentication bridging, and data-fetching strategies required for a world-class migration.

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

Laravel has been the gold standard for rapid backend development for over a decade. Its "batteries-included" philosophy is unmatched. However, as the web moves toward the edge, the demand for ultra-responsive, highly interactive, and SEO-optimized frontends has led many elite teams to a crossroads. If you’re feeling the limitations of Blade templates or the overhead of Inertia.js in complex state management, it’s time to talk about Next.js.

Migrating from Laravel to Next.js isn't just about changing a view engine; it’s about shifting from a monolithic mindset to a headless architecture. In this guide, I’m going to show you how to decouple your frontend while keeping your Laravel backend as a powerful, hardened API.


1. The Strategy: Don't "Big Bang" the Migration

One of the biggest mistakes I see developers make is trying to rewrite the entire stack from scratch in one go. Unless your app is tiny, that’s a recipe for disaster.

The Pro Tip: Use the Strangler Fig Pattern. Instead of replacing everything at once, migrate individual routes or modules one by one. Use a reverse proxy (like Nginx or Vercel’s next.config.js rewrites) to serve the new Next.js pages at specific paths while the rest of the traffic still hits your Laravel Blade views.

javascript
1// next.config.js
2module.exports = {
3  async rewrites() {
4    return [
5      {
6        source: '/admin/:path*',
7        destination: 'https://legacy.your-api.com/admin/:path*', // Keep admin in Laravel for now
8      },
9    ];
10  },
11}

2. Preparing the Laravel Backend (The API Shift)

Your Laravel app needs to stop thinking in HTML and start thinking in JSON. This means shifting from Web routes to API routes and ensuring your responses are consistent.

Implement Eloquent Resources

Don't just return models directly. Use API Resources to transform your data. This creates a contract between your backend and your Next.js frontend, preventing frontend breakage when you change database column names.

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->toIso8601String(),
9    ];
10}

Handle CORS and Sanctum

Since Next.js will likely live on a different domain or subdomain during development, you must configure Cross-Origin Resource Sharing (CORS). Laravel Sanctum is my go-to for this. It handles SPA authentication via cookies, which is significantly more secure than storing JWTs in localStorage.


3. Setting Up the Next.js Powerhouse

When you initialize your Next.js project, go with the App Router and TypeScript. If you’re a world-class developer, you don't skip types.

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

The Data Fetching Strategy

In Laravel, you’re used to $users = User::all() and passing it to a view. In Next.js, you have two primary options:

  1. Server Components (RSC): Fetch data directly on the server for SEO-heavy pages.
  2. Client-side Fetching (SWR or React Query): Use this for highly interactive dashboards.

Pro Tip: I always recommend TanStack Query (React Query) for client-side state. It provides caching, revalidation, and loading states out of the box, mimicking much of the "magic" you might miss from Livewire.


4. Bridging the Authentication Gap

This is where most migrations stall. Laravel uses session-based auth; Next.js often uses token-based or third-party providers.

To maintain a seamless experience, use NextAuth.js with a custom Credentials provider that talks to your Laravel Sanctum /login endpoint.

typescript
1// app/api/auth/[...nextauth]/route.ts
2import NextAuth from "next-auth";
3import CredentialsProvider from "next-auth/providers/credentials";
4
5const handler = NextAuth({
6  providers: [
7    CredentialsProvider({
8      name: "Laravel",
9      credentials: {
10        email: { label: "Email", type: "email" },
11        password: { label: "Password", type: "password" }
12      },
13      async authorize(credentials) {
14        const res = await fetch(`${process.env.BACKEND_URL}/api/login`, {
15          method: 'POST',
16          body: JSON.stringify(credentials),
17          headers: { "Content-Type": "application/json" }
18        });
19        const user = await res.json();
20        return res.ok && user ? user : null;
21      }
22    })
23  ],
24  // Configure session logic here
25});

5. From Blade Components to React Components

Moving from Blade to React is a mental shift from imperative-ish templates to functional, declarative UI.

Laravel Blade:

blade
1@foreach($users as $user)
2    <x-user-card :user="$user" />
3@endforeach

Next.js (React):

tsx
1interface Props {
2  users: User[];
3}
4
5export default function UserList({ users }: Props) {
6  return (
7    <div className="grid gap-4">
8      {users.map((user) => (
9        <UserCard key={user.id} user={user} />
10      ))}
11    </div>
12  );
13}

Pro Tip: Use Zod for schema validation on the frontend. It allows you to mirror your Laravel Request validation, ensuring that the data you send to the API is clean before the request even leaves the browser.


6. Optimization: The Edge Advantage

The real reason we migrate to Next.js is performance. Laravel is fast, but it’s still limited by the distance between the server and the user.

Incremental Static Regeneration (ISR)

For pages like blog posts or product listings, use ISR. This allows you to generate static pages at build time but update them in the background as your Laravel data changes, without rebuilding the entire site.

tsx
1// This page will revalidate every 60 seconds
2export default async function Page() {
3  const data = await fetch('https://api.example.com/posts', { next: { revalidate: 60 } });
4  const posts = await data.json();
5  
6  return <PostGrid posts={posts} />;
7}

Image Optimization

Replace Laravel’s manual image handling with the Next.js <Image /> component. It automatically handles WebP conversion, resizing, and lazy loading—tasks that used to require Spatie’s Media Library and a lot of server-side processing.


7. Deployment Considerations

Your deployment pipeline is about to change.

  • Backend: Keep your Laravel API on Laravel Forge or Vapor.
  • Frontend: Deploy Next.js to Vercel.

Vercel’s tight integration with Next.js provides preview deployments, edge functions, and global distribution that is difficult to replicate on a standard VPS.


Final Thoughts

Migrating from Laravel to Next.js isn't about abandoning PHP; it's about using the right tool for the right job. Laravel remains the best engine for business logic, database abstraction, and background jobs. Next.js, however, is the undisputed king of the modern frontend.

By decoupling the two, you give your users a faster experience and your developers a more modern ecosystem to work in. Start small, focus on your API contracts, and embrace the power of the headless web.

Are you stuck on a specific part of your migration? Let's discuss it in the comments below.