skip to content

Server-Driven vs SPA UIs

A Laravel team picks Blade-only pages, Livewire, Inertia with React, Vue or Svelte, or a separate SPA on a JSON API. Interviewers probe the routing, state, SEO and auth trade-offs of each.

on this pageshow

explore

questions

5

In Laravel, how do Blade, Livewire, Inertia and a separate SPA on a JSON API differ in routing ownership and where UI state lives?

level: middleimportance: must knowfreq 50%

answer

  1. who writes the URL table
  2. Inertia keeps Laravel routes and controllers
  3. Livewire state round-trips in a snapshot
  4. a separate SPA needs a client router
  5. one repository or two

basics

~20 s

Blade, Livewire and Inertia all keep routing in Laravel's route files and controllers; only a separate SPA moves routing into a client-side router over a JSON API. UI state lives in the server render (Blade), a component snapshot (Livewire), or client components (Inertia, SPA).

solid answer

~50 s

With **Blade-only** pages, `routes/web.php` maps URLs to controllers that return views, and each interaction is a new full page, so state lives in the database, session and query string. **Livewire** keeps the same routes, but components hold public properties whose values travel to the browser and back in a signed snapshot on every update — state is serialized, not kept in server memory. **Inertia** keeps Laravel's routes and controllers too; a controller returns `Inertia::render('page', $props)` and the React, Vue or Svelte page holds client-side state, with navigation done by XHR requests that return the next page's props. A **separate SPA** owns routing in its own router, calls a JSON API (usually `routes/api.php` with API resources), and holds all UI state in the browser — with a second repository, a second deploy and an API contract to version.

code

php · 15 lines
php
<?php

use App\Models\Booking;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

// Blade: Laravel owns the URL and returns full HTML
Route::get('/bookings', fn () => view('bookings.index', [
    'bookings' => Booking::latest()->paginate(),
]))->middleware('auth');

// Inertia: Laravel still owns the URL; the page component gets props
Route::get('/calendar', fn () => Inertia::render('calendar', [
    'bookings' => Booking::upcoming()->get(),
]))->middleware('auth');

go deeper

for a junior

Remember that Blade, Livewire and Inertia all use routes/web.php and controllers, while a separate SPA has its own router and calls an API.

for a middle

Explain where state lives in each: new HTML per request, a round-tripped snapshot, client-side props, or a client store over an API.

for a senior

Tie routing ownership to security and delivery: per-route middleware versus per-endpoint API checks, one deploy versus two.

for a principal

Frame the choice as how many clients the backend must serve and how many codebases the team can sustain.

## The four shapes Laravel's docs frame the frontend choice as **PHP-first** (Blade, optionally with Livewire) versus **JavaScript-first** (React, Vue or Svelte, joined to Laravel through Inertia). A fourth shape sits outside that framing: a completely **separate SPA or mobile client** talking to Laravel over a JSON API. The two questions that separate them most cleanly are *who decides what a URL means* and *where the data for the current screen lives between interactions*. ## Routing ownership | Approach | Where routes are defined | What a controller returns | Client router? | |---|---|---|---| | Blade-only | `routes/web.php` | `view('orders.index', ...)` — full HTML | none | | Livewire | `routes/web.php` (routes may point straight at a component) | a page containing components | optional SPA-style navigation via `wire:navigate` | | Inertia | `routes/web.php` | `Inertia::render('orders/index', $props)` | Inertia's client visits, but URLs still come from Laravel | | Separate SPA | the SPA's own router | JSON from `routes/api.php`, usually through API resources | yes, fully client-owned | The first three share a property that matters in practice: **Laravel is the single source of truth for URLs, middleware and authorization per route**. A route's `auth` or `can:` middleware protects the page itself. With a separate SPA, the client decides which screen to show, and the API must protect each endpoint independently — the client's route guards are only UX. ## Where state lives - **Blade-only**: effectively stateless screens. Every form post or link click fetches a new HTML document; anything that must survive goes in the database, the session, flash data or the query string. - **Livewire**: a component's public properties are rendered into HTML, and on each interaction the browser sends a **snapshot** of that state back with the action; the server rehydrates the component, runs the action, re-renders and returns a new snapshot. A checksum stops tampering, and properties marked `#[Locked]` cannot be changed from the browser. Nothing stays in server memory between requests. - **Inertia**: props arrive as JSON with each visit, then live in the page component's client state. Local interactions (toggles, tabs, a drag in a calendar) cost nothing on the server until you submit a form or visit another page. - **Separate SPA**: all UI state is client-side, usually with a store and a data-fetching cache; the server keeps only data and authentication. ## Repository and contract - **Blade, Livewire, Inertia**: one repository, one deploy, no public API to version. Changing a prop is a same-commit change on both sides. - **Separate SPA**: two codebases, two build pipelines and an API contract. Changes need coordination or versioning — the cost Laravel's own frontend docs cite as the reason Inertia exists. ## Choosing, briefly 1. Mostly forms and tables, PHP team → Blade, adding Livewire where screens need interactivity. 2. Rich client interaction, a team fluent in React, Vue or Svelte, but no separate API consumers → Inertia. 3. Several clients (web plus mobile, or third parties) must share one backend → an API is needed anyway; a separate SPA becomes reasonable, or Inertia for the web beside an API for mobile. ## Mixing approaches These are not exclusive choices inside one application. Because Blade, Livewire and Inertia all answer ordinary routes in `routes/web.php`, a single app can serve: - Blade pages for marketing and documentation; - Livewire components for admin CRUD screens; - Inertia pages for the one or two screens that need a rich JavaScript UI; - a `routes/api.php` for a mobile client. What is hard to mix is a **separate SPA** with the others for the same screens, because it moves routing out of Laravel entirely. Teams usually pick one primary approach for consistency and allow exceptions deliberately. ## Traps interviewers listen for - Saying Inertia "needs an API" — it does not; controllers pass props directly. - Saying Livewire "keeps components alive on the server" — state round-trips with every request. - Treating client-side route guards in an SPA as security.

  • With Inertia, where do authorization checks for a page belong?
    On the Laravel route or controller, exactly as with Blade: `auth` and `can:` middleware, policies or `Gate` checks run before `Inertia::render` is called. The React, Vue or Svelte page only receives props the controller chose to send, so hiding a button in the page is UX, not protection.
  • Why does a Livewire component's state survive between clicks if the server keeps nothing in memory?
    Livewire serializes the component's public properties into a snapshot embedded in the page. Each interaction posts that snapshot back with the action; the server rebuilds the component from it, runs the method, re-renders and returns a new snapshot. A checksum rejects snapshots altered in the browser.

Blade is a restaurant that brings a fresh plate for every order. Livewire is a waiter who carries your whole order slip back to the kitchen every time you change one item. Inertia hands you a menu you can rearrange at the table, but the kitchen still decides what is on it. A separate SPA is a takeaway: you run your own table and phone the kitchen for each dish.

saying these in an interview costs you the question

  • Inertia requires building a REST API for every page
  • Livewire keeps each component instance in server memory between requests
  • A separate SPA's route guards are enough to protect data
  • Livewire replaces Laravel's routes with its own client-side router
  • Blade-only apps cannot keep any state between requests
open as a page

In Laravel front ends, how do server round trips per interaction differ between Livewire, Inertia and a client-side SPA, and when does that matter?

level: middleimportance: should knowfreq 34%

basics

~20 s

In Livewire, every action or live-bound input change is a server request, so latency shows per interaction unless Alpine handles it. Inertia pays one request per visit or form submit; a separate SPA pays per API call it makes.

open as a page

For public, search-indexed pages in a Laravel app, how do Blade, Livewire, Inertia with or without SSR, and a separate SPA compare in the HTML the first response contains?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Blade and Livewire send fully rendered HTML first. Inertia without SSR sends page JSON and an empty mount element, so content appears only after JavaScript runs; Inertia SSR adds a separate JavaScript process. A separate SPA needs its own SSR.

open as a page

How does choosing Livewire or Inertia versus a separate SPA or a mobile client change the way a Laravel app authenticates users?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Blade, Livewire and Inertia pages are served by Laravel itself, so they use ordinary session cookies and CSRF protection from the web middleware group. A separate SPA can still use sessions through Sanctum if it shares the top-level domain; a mobile app needs API tokens.

open as a page

How would you choose the UI approach for a startup's Laravel scheduling SaaS with a small team, a public booking page, a drag-and-drop staff calendar and a possible mobile app?

level: principalimportance: should knowfreq 30%

basics

~20 s

Start from team skills and the hardest screen. A drag-and-drop calendar favours client-side state (Inertia), the public booking page favours server-rendered HTML, and a future mobile app needs a token API anyway, so it does not force a separate SPA.

open as a page