Skip to main content

Vue 3 + Inertia.js in 2026: The Combo That Makes SaaS Actually Deliverable

Author
Ignacio AmatIgnacio Amat
Published
Reading Time5 min
Laravel, Inertia.js and Vue 3 logos working together for SaaS development

I’ve been building SaaS with Laravel for years, using everything: classic Blade, full SPAs with Vue Router + REST API, Livewire. Since I adopted Inertia.js with Vue 3, my feature delivery speed effectively doubled. That’s not an exaggeration.

No separate API needed. No Vue Router. No manual type synchronization between backend and frontend. The data tunnel between Laravel and Vue disappears, and you’re left with only what matters: building features.


How the Data Flow Works (and Why It Matters)

In a traditional SPA, each screen requires:

  1. Laravel Controller → API Resource → JSON → Axios → Vue Store → Component → Render.

With Inertia:

  1. Laravel Controller → Inertia::render() → Vue Component + props → Render.

Inertia SSR ensures search engines receive full HTML while users enjoy instant SPA-like navigation. No SEO loss, no UX sacrifice.

// app/Http/Controllers/DashboardController.php
class DashboardController extends Controller
{
    public function __invoke(): Response
    {
        $user = auth()->user();
  
        return Inertia::render('Dashboard/Index', [
            'stats' => [
                'total_revenue' => (float) Revenue::totalForUser($user),
                'active_subscriptions' => $user->subscriptions()->active()->count(),
                'pending_invoices' => $user->invoices()->pending()->count(),
            ],
            'recentActivity' => Activity::forUser($user)
                ->take(10)
                ->get()
                ->through(fn ($a) => [
                    'id' => $a->id,
                    'action' => $a->action,
                    'description' => $a->description,
                    'created_at' => $a->created_at->diffForHumans(),
                ]),
            'filters' => request()->only(['period', 'status']),
        ]);
    }
}

No Resources, no manual frontend transformations. Data arrives as typed props.


What Actually Speeds You Up: Proven Patterns

1. Forms Without External Libraries

No FormKit, VeeValidate or Zod needed. Use @inertiajs/vue3’s useForm:

<script setup>
import { useForm } from '@inertiajs/vue3';

const form = useForm({
  name: '',
  email: '',
  plan: 'monthly',
  terms: false,
});

function submit() {
  form.post('/subscribe', {
    preserveScroll: true,
    onSuccess: () => form.reset(),
    onError: (errors) => console.table(errors),
  });
}
</script>

<template>
  <form @submit.prevent="submit">
    <TextInput v-model="form.name" :error="form.errors.name" />
    <TextInput v-model="form.email" type="email" :error="form.errors.email" />
    <PrimaryButton :disabled="form.processing">
      {{ form.processing ? 'Processing...' : 'Subscribe' }}
    </PrimaryButton>
  </form>
</template>

form.processing prevents double submission. form.errors automatically maps Laravel validation errors. preserveScroll keeps position on reload.

2. Authorization Without Duplicating Logic

// app/Http/Controllers/InvoicesController.php
class InvoicesController extends Controller
{
    public function index(): Response
    {
        $this->authorize('viewAny', Invoice::class);

        return Inertia::render('Invoices/Index', [
            'invoices' => Invoice::forUser(auth()->user())
                ->with('items')
                ->latest()
                ->paginate(15)
                ->through(fn ($invoice) => [
                    'id' => $invoice->id,
                    'total' => $invoice->total_cents / 100,
                    'status' => $invoice->status,
                    'can' => [
                        'pay' => auth()->user()->can('pay', $invoice),
                        'download' => auth()->user()->can('download', $invoice),
                        'cancel' => auth()->user()->can('cancel', $invoice),
                    ],
                ]),
        ]);
    }
}

In the frontend, conditionally render based on can:

<button v-if="invoice.can.pay" @click="pay(invoice.id)">Pay Now</button>

No need to replicate policies in JavaScript. Rules live in Laravel and arrive as props.

3. Real SSR for SEO

Inertia SSR isn’t a “compatibility mode.” It’s native SSR with Node:

npm install @inertiajs/server
node artisan inertia:start-ssr

Crawlers receive full HTML with meta tags, titles, descriptions and content. Your public pages (landing, docs, pricing) index like any traditional site, while logged-in users navigate with SPA fluidity.


Testing: Pest + inertia-testing

// tests/Feature/Http/Controllers/DashboardControllerTest.php
use Inertia\Testing\AssertableInertia;

test('dashboard shows correct stats for authenticated user', function () {
    $user = User::factory()->create();
    Subscription::factory()->for($user)->active()->create(['plan' => 'pro']);
  
    $response = $this->actingAs($user)->get('/dashboard');
  
    $response->assertInertia(fn (AssertableInertia $page) => $page
        ->component('Dashboard/Index')
        ->has('stats')
        ->where('stats.active_subscriptions', 1)
        ->has('recentActivity')
        ->where('filters', [])
    );
});

test('dashboard redirects guests to login', function () {
    $response = $this->get('/dashboard');
    $response->assertRedirect('/login');
});

test('dashboard paginates recent activity', function () {
    $user = User::factory()->create();
    Activity::factory()->for($user)->count(25)->create();
  
    $response = $this->actingAs($user)->get('/dashboard?page=2');
  
    $response->assertInertia(fn (AssertableInertia $page) => $page
        ->component('Dashboard/Index')
        ->has('recentActivity.data', 10)
        ->where('recentActivity.current_page', 2)
    );
});

No need to mock Axios, Vue stores or API calls. The test talks directly to the rendered page and verifies its props.


When NOT to Use Inertia (Honestly)

Inertia is my default, but it’s not always the right call:

  • Separate frontend/backend teams: if the frontend team doesn’t touch PHP, a traditional API with Vue Router is cleaner.
  • Offline-first / aggressive PWA: Inertia depends on the server for navigation. For apps that must work offline, go SPA with a service worker.
  • Multiple clients (web + mobile): if you need APIs for iOS/Android alongside the web, consider whether maintaining two paths is worth it or if you unify on API.
  • Heavy real-time: WebSockets work with Inertia, but if your app is 90% real-time (chat, trading dashboard), a native SPA with WS gives more control.

For everything else (SaaS, admin panels, internal tools, MVPs, landing pages with SSR), Inertia is unbeatable.


Conclusion

Laravel + Inertia + Vue 3 isn’t a trend: it’s the most productive way I know to build complete web applications without duplicating backend/frontend logic.

If your team uses Laravel and you’re deciding how to structure the frontend, my recommendation is clear: try Inertia with a real feature (not a tutorial). You’ll see the friction between backend and frontend disappear.


Need Someone Who Already Masters This Stack?

I’ve been building complete SaaS products with Laravel, Inertia and Vue 3 for years — from database to deploy. If your team needs a senior profile who delivers fast without accumulating technical debt:

  • Stack: Laravel 11, PHP 8.3+, Vue 3 + TS + Inertia, Pest, Vite, Docker, GitHub Actions.
  • Modalities: EOR (Deel, Remote, Oyster), freelance B2B, or direct permanent if you have a Spanish entity.
  • Availability: remote Spain/EU/US, 3+ month projects or indefinite role.

View My Full Profile & Terms →


Related articles

Review my developer profile

If this article matches the kind of product work your team is facing, review my stack or professional availability.

Tell me what you need

You can reach out about a role, contract, technical collaboration, question, or general inquiry. A few lines of context are usually enough for me to reply within 24 business hours.

0/500
Availability