jobs.at Recruiting GmbH
SPA without API
Description
Jürgen Ratzenböck von jobs.at spricht in seinem devjobs.at TechTalk "SPA without API" darüber, welche Vorteile und Challenges dieser Ansatz mit sich bringt und gibt technische Einblicke in das Thema.
By playing the video, you agree to data transfer to YouTube and acknowledge the privacy policy.
Video Summary
In “SPA without API,” Jürgen Ratzenböck shows how to deliver an SPA experience on an existing server‑rendered Laravel app with Vue.js without building a separate REST/GraphQL API. He demonstrates Inertia.js as the glue that preserves server-side routing, controllers, validation, and session auth, returns JSON with component and props, and on the client renders Vue components with navigation, form posts ($inertia.post), redirects, flash/validation errors, and asset versioning. The key takeaway: full APIs add overhead (two repos, slower initial load, HTTP chatter, auth/CORS/CSP, build/deploy setup, SEO), while Inertia reduces complexity for a single web client in a monorepo; if you have multiple clients, a real API is still the better fit.
SPA without API: How Jürgen Ratzenböck (jobs.at Recruiting GmbH) marries Laravel and Vue with Inertia.js
Context: A growing job platform and a pragmatic architectural choice
In “SPA without API,” Jürgen Ratzenböck from jobs.at Recruiting GmbH explained how his team brought a single‑page experience into an existing server‑rendered Laravel application—without building a standalone REST or GraphQL API. From our DevJobs.at editorial vantage point, the talk stood out because it focused less on dogma and more on practical decision‑making: When is a classic SPA+API architecture the right move, and when is an adapter like Inertia.js the faster, lighter path?
Before diving into tech, Jürgen set the stage. jobs.at is a performant job platform with a team that has grown beyond 20 people. The company is based in Linz (with flexible home office), and a new Vienna office is in the works. For product context relevant to the talk: about 70,000 jobs in inventory, roughly 830,000 sessions per month, and a fair, transparent pay‑per‑click billing model for customers rather than fixed upfront ad prices regardless of performance. Technically, the stack centers on PHP with the Laravel ecosystem on the backend and Vue.js on the frontend.
The central question of the session: How can you deliver an SPA‑like UX—fast, app‑like, interactive—inside a server‑rendered system, without taking on the complexity and overhead of a fully decoupled API backend?
SPAs and APIs: Shared foundations
Jürgen began with a concise refresher. A Single‑Page Application (SPA) boots from one HTML file, usually with no app‑specific markup. The client does the “heavy lifting”: routing, rendering, and state. Data comes from the server as JSON, traditionally over REST but increasingly over GraphQL. The backend becomes a CRUD‑centric data provider responding to XHR/JSON rather than serving HTML.
On the backend, multiple stacks are viable: Node.js, Laravel (PHP), and even backend‑as‑a‑service like Firebase. On the frontend, Vue.js, React, and Angular dominate. This separation is common—but not the only option. The talk emphasized that there is a spectrum between fully server‑rendered and fully client‑rendered approaches.
Why SPAs? Benefits—and who they benefit
Jürgen laid out the classic pro arguments:
- No full page refresh: You can update specific parts of the page, making interactions snappy and fluid.
- App‑like UX: Reactive rendering plus optional PWA features (add to home screen, offline via service workers) can feel “native.”
- Strong developer experience: Frameworks bring structure, reactivity, and scalability—much nicer than raw DOM manipulation.
- Multiple clients on one API: Web and mobile apps can share the same backend.
- Simpler backend: Mostly CRUD, potentially serverless (e.g., Lambda), with less server infrastructure to manage.
But these benefits come with trade‑offs.
Where SPA+API hurts: The typical friction points
He was equally direct about the downsides:
- Two repos/projects: Separate API and frontend codebases increase setup and ongoing coordination.
- Slower initial load: First HTML, then JavaScript, then data XHR—extra roundtrips add latency. SSR can mitigate this, but introduces complexity of its own.
- More HTTP chatter: Many small requests and roundtrips.
- Heavier build & deploy: CI/CD needs to orchestrate both frontend and API.
- Auth and security overhead: Moving from session auth to JWT, dealing with CORS and Content Security Policy (often on separate subdomains), etc.—all solvable, but all work.
- SEO risk: If content renders only on the client, indexing is not guaranteed. For public pages, server‑side rendering often remains a must.
These costs are well spent when you truly need multi‑client support. If you don’t, they can be unnecessary overhead.
jobs.at starting point: Laravel SSR, a growing user profile
The jobs.at frontend is a classic Laravel app: server‑rendered templates, tuned for performance and accessibility, deliberately avoiding extra third‑party JavaScript. Session‑based authentication is in place, but the user profile had been minimal—login and personal data, nothing more.
The new project goal: expand the profile significantly—starting with document management (CV, certificates) and reusing those documents during application flows, with the expectation that the profile will grow over time. Two constraints shaped the approach:
- Highly mobile optimized: Most users are on their phones; uploading and managing documents on the go must be smooth.
- SEO irrelevant: The profile lives behind authentication; search indexing does not apply here.
In short, the team wanted a modern SPA feel for the profile area without overhauling the backend or abandoning Laravel’s session auth.
Target approach: SPA feel, no API split
Given those constraints, the desired direction was clear: use Vue.js for a snappy, app‑like UX in the profile area, while keeping Laravel routing, validation, and sessions. Rather than splitting out a REST/GraphQL backend, the team wanted to keep the monolith intact—and ship quickly.
The solution came from the Laravel ecosystem: Inertia.js.
Inertia.js: An adapter, not yet another framework
Inertia.js is not a new frontend framework. It’s a bridge between backend and frontend that lets you keep server‑side routing (e.g., Laravel controllers) while delivering a client‑side SPA experience with Vue, React, or Svelte. Inertia handles the glue—navigation, props hydration, redirects—so you don’t have to re‑implement client‑side routing and state plumbing from scratch.
Jürgen quoted Inertia’s tagline: “building single-page apps without building an API.” That’s precisely what the project needed: keep the monorepo, add Vue in the profile, and avoid a separate API layer.
The Inertia request/response flow
He walked through the flow step by step:
- Initial request: The browser asks for an HTML page; the server returns HTML. The DOM includes a root container where the SPA will mount. Inertia decorates that root with a
data-pageattribute containing JSON that identifies the Vue component to render and the props it needs. - Bootstrapping: The frontend downloads JavaScript and boots the Vue app. During startup, Inertia reads
data-page, resolves the component, and renders it. From this point, the app behaves like an SPA. - Subsequent navigation: Further interactions happen via XHR. Inertia adds an X‑Inertia header so both sides recognize Inertia requests. The server responds with JSON specifying the component, props, URL, and optionally an asset version (for cache busting). Inertia swaps components accordingly—no full page reload.
On the server, controller logic and data fetching stay the same. The key difference is in the response: instead of returning a server template, you return an Inertia response with the component name and props. On the client, you don’t write Axios boilerplate to fetch page data—Inertia hydrates the props directly into the component.
Backend setup: Laravel remains Laravel
Integrating Inertia into Laravel is minimal and familiar:
- Install the Inertia adapter via Composer.
- Add an Inertia directive to your base layout where the SPA should mount. This renders the root container with the
data-pageattribute. - Keep your controllers, routes, and validation as is. Only switch the return type to an Inertia response that specifies the component and its data.
- Middleware: Inertia provides middleware to handle redirects, flash data, and asset versioning. If the asset build version has changed, Inertia triggers a full reload so the client downloads the new bundles.
The result: the standard Laravel development flow, with Inertia translating the response into hydrated props for the client component.
Frontend setup: Boot Vue, resolve components
On the client side, install the Inertia adapter via NPM. Inertia supports Vue, React, and Svelte; Jürgen noted that Angular support is in progress. In Vue, you configure Inertia during app bootstrap and provide a resolve function that maps the server‑provided component name to a Vue file (e.g., in your profile/pages folder). Then you mount the app into the root element.
Individual pages are plain Vue components: template plus script with props. The prop names match the keys sent by the server. You don’t need a created hook to fetch page data—props are passed in, saving boilerplate and promise/error handling.
Forms, redirects, and flash messages
Forms are a common scenario in profile areas. Jürgen showed the pattern they use:
- On submit, call a global Inertia helper to post to the endpoint along with the form data object. Optional callbacks like onStart and onFinish let you control loading indicators.
- On the server, use a standard Laravel redirect and pass a success message (flash). The Inertia middleware recognizes the context and packages the flash data so it appears in the client’s page data.
- In the Vue component, access the message via a global page reference and render it (for example, as a toast).
The key benefit is that the traditional Laravel redirect flow still applies, while Inertia preserves SPA navigation semantics.
Auth, CORS, CSP: Complexity avoided by design
A powerful but quieter win in this approach is everything the team did not have to build. Jürgen reminded the audience that a decoupled API typically entails JWT instead of session auth, CORS and CSP policies (often across subdomains), and operational duplication. Because the profile runs inside the same Laravel app, session‑based authentication stays, and all that extra scaffolding disappears. For the team, that meant fewer moving parts and faster delivery.
SEO considerations: Prioritize by context
Because the profile lives behind login, SEO does not matter here. Jürgen highlighted this because it changes the calculus: For public, indexable pages, SSR or hybrid rendering is often necessary. For authenticated, interactive areas, a client‑heavy approach can be the right trade‑off—without taking on the burden of a separate API.
Developer experience: Low learning curve, maintainable structure
Team feedback was clear: getting started with Inertia was easy. You don’t learn a brand‑new framework; you learn a few glue concepts on both sides. Laravel validation, routing, and controller patterns remain intact—huge leverage for teams who already maintain a solid server‑rendered app and want to modernize parts of it.
When to choose Inertia.js—and when to build an API
One of the clearest takeaways from “SPA without API”:
- If your web app is the only client (and will be for the foreseeable future), Inertia.js is a strong fit. You start faster, carry less infrastructure, and keep development flows smooth.
- If you plan multiple clients (a mobile app, a voice skill, etc.), a real API is the right backbone. Inertia focuses on the web client; it’s not designed as a universal multi‑client backend.
As Jürgen put it, this isn’t a black‑and‑white decision. Adapters like Inertia open a pragmatic middle path.
Practical guidance drawn from the session
The talk yields practical, immediately applicable guidance:
- Keep server routes in charge: Laravel controllers continue to orchestrate page data. Only the response type changes.
- Structure components cleanly: The component name bridges server responses and Vue files. A clear pages/components directory structure makes mapping robust.
- Design props deliberately: What your controllers return becomes component props. Keep them clean and flat to maintain component simplicity.
- Pass validation errors as usual: The standard Laravel pattern (errors in session/flash) plays well with Inertia; the middleware hydrates errors and messages into page data automatically.
- Treat asset versions seriously: Inertia uses them for cache busting. Ensure your build pipeline updates the version consistently.
- Test mobile UX early: For uploads and forms, interaction details matter. Use Inertia’s lifecycle callbacks to drive loading states and provide crisp feedback.
- Know the boundaries: For public SEO pages or multi‑client ecosystems, use SSR/hydration or build a classical API. Use Inertia where it truly fits.
Conclusion: “SPA without API” as a pragmatic middle ground
“SPA without API” by Jürgen Ratzenböck (jobs.at Recruiting GmbH) showcases a well‑calibrated architectural decision: modern, reactive UX with Vue—without the overhead of a standalone API layer. The combination of Laravel SSR for the broad public site and an Inertia‑powered SPA feel in the private profile area matches the product’s real needs: mobile‑optimized, maintainable, and quick to ship.
Three concise takeaways for engineering teams:
- Validate whether you truly need a dedicated API—or whether Inertia inside your monolith achieves your goals faster and more robustly.
- Tie architectural choices to product realities: SEO needs, number of clients, team skills, and time‑to‑market.
- Leverage the strengths of your existing platform (routing, validation, sessions) instead of replacing them prematurely.
Or, borrowing the Inertia tagline Jürgen cited: “building single-page apps without building an API.” That’s exactly what jobs.at implemented for its profile area—and exactly why this approach will resonate with teams in similar situations.