Laravel
Maintainable backends: structure, Eloquent, queues, authorization and SaaS patterns.
- Application structure
- Eloquent
- Queues
- Testing
Vue.js has been part of several of my production applications, particularly SaaS platforms and business systems built alongside Laravel. I use it for product interfaces where maintainability, component boundaries, API integration and long-term evolution matter.
Composition API · State ownership · Laravel + Vue · Accessibility
How I think about it
Vue makes it easy to start quickly, but maintainability still depends on how components, state, business logic and API interactions are structured as the product grows.
Key experience
Scope
Deep dive
Single-file components make it pleasantly easy to keep markup, behaviour and styles together — which is also how 800-line components happen. I split by responsibility: page components that load data and compose a screen, domain components that understand one piece of the product (an invoice table, a member list), and reusable UI components that know nothing about the business.
Abstraction comes after real repetition. A generic <DataTable> with thirty props is harder to change than three focused tables sharing a few small building blocks.
Composables are where the Composition API pays off: behaviour — loading members, managing filter state, tracking an upload — moves out of components into functions that can be reused and tested on their own. Components become mostly markup and wiring.
Lifecycle concerns travel with the composable. If it starts a timer, subscribes to an event or opens a connection, it also cleans up in onUnmounted, so the component using it can't forget.
// composables/useMembers.ts — behaviour, no markup
export function useMembers(organizationId: Ref<number>) {
const members = ref<Member[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
async function load() {
loading.value = true;
error.value = null;
try {
members.value = await api.get(`/organizations/${organizationId.value}/members`);
} catch {
error.value = "Members could not be loaded.";
} finally {
loading.value = false;
}
}
watch(organizationId, load, { immediate: true });
return { members, loading, error, reload: load };
} Local state stays in the component. State shared by a parent and its children travels through props and events. A store is for state that many unrelated parts of the app read — the authenticated user, the active organization, permissions, feature flags.
Putting everything in a global store feels organised at first and becomes a coupling problem later: any component can change anything, and nobody knows what depends on what. Server data is better handled as cached request state close to where it's used than copied into a store by hand.
Every screen that loads data needs loading, empty, error and success states. List screens add pagination and filters that should survive a refresh — so filters belong in the URL query, not only in component state.
Authentication, error parsing and retries for idempotent requests live in one API client. Stale responses are a classic SPA bug: when filters change quickly, an older request can resolve last and overwrite newer results, so superseded requests are cancelled or ignored.
Complex forms — dynamic rows, conditional sections, multi-step flows — need one explicit form model rather than many unrelated refs. Client-side validation gives quick feedback; the server's validation response is still the source of truth, and its field errors map back onto the same inputs.
Errors are shown next to the field and linked with aria-describedby, and focus moves to the first invalid field on submit.
Laravel and Vue work well together when the boundary is explicit. Laravel owns business rules, authorization and validation; Vue owns interaction and presentation and consumes a documented API. Duplicating business rules in the frontend — price calculations, permission logic — guarantees they will drift apart.
For a first-party SPA on the same domain, session-based authentication with CSRF protection is usually simpler and safer than storing tokens in the browser. Laravel's validation errors come back in one consistent shape the form layer understands. Deployment needs care too: frontend assets are versioned with the backend release, so an old cached bundle never talks to a changed API.
Vue
interaction, presentation
API
validated, authorized
Laravel domain
business rules
Database
tenant-scoped data
Routes mirror product areas, with nested routes for sections that share a layout — an organization's settings, a project and its tabs. Route-level data loading gives each page clear loading and error states. Navigation guards hide areas a user can't access, but they're a convenience: the API still enforces every permission.
Navigation state worth keeping — the active tab, filters, the current page — belongs in the URL, so it survives a refresh and can be shared.
Vue renders whatever markup you give it, so accessibility comes down to the components: native elements first, dialogs that move and return focus, menus and custom selects that follow established keyboard patterns, and live regions for asynchronous updates. In a single-page app, route changes should update the document title and move focus, or screen-reader users won't know the page changed.
Route-level lazy loading keeps the initial bundle small. Long lists need stable keys and, beyond a few hundred rows, pagination or virtualization rather than rendering everything. Making large data structures deeply reactive is a hidden cost — data that's only displayed doesn't need fine-grained reactivity. Beyond that, I optimise what the profiler shows, not what looks slow in code review.
Consistency matters more than any single pattern: one way to call the API, one way to handle errors, one place for form logic, one folder structure organised by feature. Composables and shared components remove duplication only when they're genuinely shared; otherwise they add indirection.
In practice
Real products
Vue.js has been the frontend for Laravel-based platforms and business systems I've built and led.
Enterprise SaaS
Lead developer on a Laravel and Vue.js platform — the Vue frontend integrated with the platform's REST APIs in a multi-tenant, multi-user context.
Full-stack and lead developer roles
Web applications and internal business systems built with Laravel and Vue.js.
Maintainable backends: structure, Eloquent, queues, authorization and SaaS patterns.
Tenancy, permissions, plans, billing and the realities of running SaaS in production.
Accessible engineering: semantics, keyboard and focus, forms, testing and WCAG in practice.
Workload-first cloud architecture: serverless, storage, data, security and cost.
Build reliable web platforms, SaaS products, dashboards, APIs, and internal tools with modern full-stack technologies.
Learn moreDesign and build SaaS products from MVP to production, including architecture, user management, subscriptions, integrations and cloud infrastructure.
Learn moreWhether it's an architecture decision, a delivery problem or something specific to Vue.js, I can help you find a practical path forward.
What happens next
Share your goal
A few lines about the product, problem, or audit need.
Get a quick read
A practical reply on the best technical path forward.
Move forward
A clear scope, estimate, or concrete next step.