QUANHEX.
Architecture

Why We Chose Laravel + Inertia.js + Svelte for Our SaaS

The modern monolith approach to SaaS architecture: one codebase, no API contract, and the full power of Svelte on the frontend.

· 7 min read

When we started building a new SaaS product, we had a decision to make: separate frontend and backend with a REST or GraphQL API, or use a monolith approach where the server handles routing and the frontend gets data through server-side rendering.

We chose the latter — specifically Laravel, Inertia.js, and Svelte 5. Eight months in, we’d make the same call again.

The Problem With SPA + API for Solo Products

The canonical “React SPA + REST API” architecture has real costs that are easy to underestimate when you’re one or two people:

  • Two deployment targets to manage, monitor, and secure
  • API contract maintenance — every frontend change may require a backend change
  • Authentication complexity — JWTs, refresh tokens, CORS headers
  • Duplicated validation — once on the client, once on the server
  • Type synchronization — keeping frontend types aligned with API response shapes

For a team of 10 engineers, these costs are manageable and sometimes desirable. For a solo founder, they’re a significant tax on velocity.

What Inertia.js Actually Does

Inertia is not a framework — it’s a protocol. It sits between a server-side framework (Laravel, Rails, Django) and a client-side framework (React, Vue, Svelte) and makes them work together without a separate API.

When you navigate to a route, Laravel handles it, runs its controllers and middleware, and returns an Inertia response. Inertia intercepts subsequent navigations and fetches only the page component and props — no full page reload, no separate API endpoint.

// Laravel controller
public function dashboard(Request $request)
{
    return Inertia::render('Dashboard/Index', [
        'stats' => DashboardStats::for($request->user()),
        'recentActivity' => Activity::recent()->get(),
    ]);
}
<!-- Svelte component -->
<script>
  let { stats, recentActivity } = $props();
</script>

<DashboardLayout>
  <StatsGrid {stats} />
  <ActivityFeed {recentActivity} />
</DashboardLayout>

The controller and the component are coupled by design, and that’s fine. That coupling is the trade you’re making for simplicity.

Why Svelte 5

Svelte compiles to vanilla JavaScript. No virtual DOM, no runtime framework to ship to the browser. The output is smaller and faster than React or Vue equivalents for most component patterns.

Svelte 5’s rune-based reactivity is a genuine improvement. $state, $derived, and $effect are explicit, composable, and behave exactly like you’d expect from reading the code.

<script>
  let count = $state(0);
  let doubled = $derived(count * 2);

  $effect(() => {
    document.title = `Count: ${count}`;
  });
</script>

The learning curve is lower than React for developers new to frontend, and the performance headroom is generous.

The Tradeoffs

This architecture is not universally better. If you need a public API that third parties consume, you’ll need to build it separately regardless. If your team has deep React expertise, the productivity cost of switching to Svelte may not be worth it. If you need extreme frontend complexity — custom rendering pipelines, complex animation orchestration — the constraints of Inertia’s page-based model may chafe.

But for a product-focused SaaS where the goal is shipping features, not building infrastructure, the modern monolith earns its keep.