Building a SaaS Application with Laravel 12 & Livewire: What I Learned the Hard Way

Building a SaaS Application with Laravel 12 & Livewire: What I Learned the Hard Way

Cover
Boris Lukrece
11 May 2026 2 views 4 min read

When I started building my HR & attendance management SaaS, I thought the hardest part would be the business logic. I was wrong. The hardest part was making the right architectural decisions early — the ones that save you from rewriting everything two months later.

Here’s what I actually learned building a real SaaS product with Laravel 12, Livewire 4 (Volt), and Lemon Squeezy as a payment processor.

Laravel SaaS architecture overview Clean architecture pays off when your app starts growing

1. Start With Your Subscription Model, Not Your Features

Most developers (myself included) start building features and bolt on payments later. Big mistake.

Your subscription model shapes everything: what users can access, how you handle plan limits, how you structure your database. If you design features first, you’ll spend weeks refactoring when billing comes in.

What I did instead:

I created a PlanVariant model early on that bridges my internal plan IDs to Lemon Squeezy variant IDs, and a UserSubscription model that tracks each user’s current plan state independently.

// PlanVariant — bridges internal plans to payment processor
Schema::create('plan_variants', function (Blueprint $table) {
    $table->id();
    $table->foreignId('plan_id')->constrained()->cascadeOnDelete();
    $table->string('variant_id'); // Lemon Squeezy variant ID
    $table->enum('billing_cycle', ['monthly', 'yearly']);
    $table->timestamps();
});

This decoupling means if you ever switch payment processors, your core app logic doesn’t break.

2. Livewire Volt Is a Game Changer for Admin Panels

If you’re building a back-office or admin panel, Livewire 4 Volt with single-file components is incredibly productive. You write your PHP logic and your Blade template in one file — no more jumping between controllers, views, and form requests for simple CRUD.

Livewire component structure One file, full reactivity — that’s the Volt promise

The pattern I settled on for create/update forms:

<?php
// ⚡plans/edit.blade.php

use function Livewire\Volt\{state, mount};

state(['plan' => null, 'name' => '', 'price' => 0]);

mount(function (Plan $plan) {
    $this->plan = $plan->firstOrNew(['id' => $plan->id]);
    $this->name = $this->plan->name ?? '';
    $this->price = $this->plan->price ?? 0;
});

$save = function () {
    $this->validate(['name' => 'required', 'price' => 'required|numeric']);

    if ($this->plan->isDirty()) {
        $this->plan->save();
        $this->dispatch('alert', type: 'success', message: __('app.saved'));
    }
};
?>

The firstOrNew + isDirty() combo is clean and avoids unnecessary database writes.

3. Choose Your Payment Processor Based on Your Situation, Not Just Features

I went with Lemon Squeezy for one specific reason: it acts as a Merchant of Record. This means Lemon Squeezy handles taxes, VAT, and compliance globally — not me. As an independent developer without a registered business entity, this was non-negotiable.

Stripe is more powerful, but it puts the tax compliance burden on you. For a solo developer shipping a product, that overhead is real.

The tradeoff: Lemon Squeezy takes a higher cut (5% + $0.50 per transaction). Worth it for the peace of mind.

Webhook setup in Laravel:

// routes/web.php
Route::post('/webhooks/lemon-squeezy', [LemonSqueezyWebhookController::class, 'handle'])
    ->name('webhooks.lemon-squeezy');
// In your webhook controller
public function handle(Request $request): Response
{
    $payload = $request->all();
    $eventName = $payload['meta']['event_name'];

    match ($eventName) {
        'subscription_created' => $this->handleSubscriptionCreated($payload),
        'subscription_updated' => $this->handleSubscriptionUpdated($payload),
        'subscription_cancelled' => $this->handleSubscriptionCancelled($payload),
        default => null,
    };

    return response()->noContent();
}

Always verify the webhook signature. Always.

4. The Landing Page Is Not Optional

If you use a payment processor like Lemon Squeezy, they will ask for a live, public landing page before approving your store. Not a “coming soon” page — an actual page describing what your product does, who it’s for, and what it costs.

SaaS landing page Your landing page is your first impression AND a business requirement

Build it early. It also forces you to clarify your own value proposition, which always improves the product.

5. Don’t Skip the Permission Layer

Spatie’s laravel-permission package is the standard for a reason. Set it up from day one — defining roles and permissions retroactively is painful.

My setup: each subscription plan maps to a set of permissions. When a user upgrades or downgrades, permissions are synced automatically via a job triggered by the Lemon Squeezy webhook.

// Sync permissions on subscription change
$user->syncRoles([$newPlan->role]);

Simple, auditable, and easy to extend.

Key Takeaways

DecisionWhat I’d do again
Subscription model first✅ Absolutely
Livewire Volt for admin✅ Saves so much time
Lemon Squeezy as MoR✅ If you’re a solo dev
Spatie permissions from day 1✅ Non-negotiable
Landing page early✅ Required anyway

Building a SaaS is mostly about making decisions you won’t regret in 6 months. The tech stack matters less than the architecture decisions you make in week one.

If you’re building something similar or have questions about any of these choices, drop a comment — happy to go deeper on any of these points.

Boris Lukrece
Boris Lukrece

@borislukrece

Web developer based in Côte d’Ivoire 🇨🇮 — I build SaaS products, web tools, and community platforms. Specialized in Laravel, Livewire & Tailwind CSS. Currently shipping an HR management SaaS. I write about real dev experiences, not just theory. 👨🏾‍💻

Comments

0

Sign in to leave a comment

No comments yet. Be the first to comment!

About this article

English
11 May 2026
2 views
4 min read
1 like