Skip to main content

PBS Planning - Technology Stack Recommendation v3.0

Document Version: 3.0 Date: February 10, 2026 Status: CURRENT RECOMMENDATION

Executive Summary

After thorough evaluation of React, Vue, and Svelte, we are proceeding with Svelte as our frontend framework. This decision prioritizes simplicity, performance, and developer experience while maintaining flexibility to pivot to React if needed.

Chosen Stack:

  • Backend: Laravel 11
  • Frontend: Svelte 5
  • Bridge: Inertia.js
  • Styling: Tailwind CSS v3+
  • Database: PostgreSQL 15
  • Architecture: Modern monolith (no separate API)

Why Svelte for PBS Planning

1. True Simplicity (Aligns with "Simplicity First")

<!-- Svelte: Clean and intuitive -->
<script>
let project = { name: '', budget: 0 }
</script>

<input bind:value={project.name} />
<input bind:value={project.budget} type="number" />

No hooks, no rules, no .value - just JavaScript variables that work.

2. Superior Performance

  • No Virtual DOM: Compiles to vanilla JavaScript
  • Smaller bundles: ~10KB vs React's ~45KB
  • Faster runtime: Critical for mobile crew portal
  • Less memory usage: Important for older devices

3. Perfect for Forms (80% of PBS)

<!-- Two-way binding is built-in and natural -->
<input bind:value={formData.name} />
<select bind:value={formData.role}>
<option value="camera">Camera</option>
<option value="sound">Sound</option>
</select>
<textarea bind:value={formData.notes} />

Compare to React's verbose event handlers - Svelte reduces form code by ~40%.

4. Transparent Compilation

  • Svelte's "magic" happens at build time
  • You can inspect the compiled output
  • Easier debugging than React's runtime magic
  • No hidden state management complexity

5. Developer Experience

  • 89% satisfaction rate (State of JS 2024)
  • Gentle learning curve
  • Built-in animations and transitions
  • Excellent documentation

Complete Technology Stack

Core Stack

LayerTechnologyVersionPurpose
Backend FrameworkLaravel11.xPHP framework, API, business logic
Frontend FrameworkSvelte5.xUI components and interactivity
Frontend BridgeInertia.js1.0+Connects Laravel to Svelte without API
CSS FrameworkTailwind CSS3.4+Utility-first styling
DatabasePostgreSQL15+Primary data storage
Cache/QueueRedis7.0+Session, cache, job queues

Development Tools

ToolTechnologyPurpose
Build ToolViteFast development and building
Package Managernpm/pnpmJavaScript dependencies
PHP DependenciesComposerPHP package management
Code FormattingPrettier + ESLintJavaScript formatting
PHP FormattingLaravel PintPHP code style
Testing (Frontend)Vitest + PlaywrightUnit and E2E tests
Testing (Backend)Pest PHPLaravel testing

Laravel Packages

{
"laravel/framework": "^11.0",
"laravel/sanctum": "^3.3",
"inertiajs/inertia-laravel": "^1.0",
"spatie/laravel-permission": "^6.0",
"spatie/laravel-multitenancy": "^3.0",
"barryvdh/laravel-dompdf": "^2.0",
"maatwebsite/excel": "^3.1"
}

JavaScript Dependencies

{
"svelte": "^5.0",
"@inertiajs/svelte": "^1.0",
"tailwindcss": "^3.4",
"@tailwindcss/forms": "^0.5",
"@tailwindcss/typography": "^0.5",
"vite": "^5.0",
"@sveltejs/vite-plugin-svelte": "^3.0",
"zod": "^3.22",
"svelte-forms-lib": "^2.0",
"date-fns": "^3.0"
}

Architecture Pattern: Laravel + Inertia + Svelte

How It Works

  1. No REST API Needed

    // Laravel Controller
    public function index()
    {
    return Inertia::render('Projects/Index', [
    'projects' => Project::where('company_id', $companyId)
    ->with('client')
    ->paginate(20)
    ]);
    }
  2. Svelte Receives Props Directly

    <!-- Projects/Index.svelte -->
    <script>
    export let projects = []

    function deleteProject(id) {
    $inertia.delete(`/projects/${id}`)
    }
    </script>
  3. Form Submissions

    <script>
    import { useForm } from '@inertiajs/svelte'

    const form = useForm({
    name: '',
    client_id: '',
    start_date: '',
    budget: 0
    })

    function submit() {
    $form.post('/projects')
    }
    </script>

Benefits

  • Single codebase
  • No CORS issues
  • Session-based auth (more secure)
  • Server-side validation
  • SEO-friendly
  • Simpler deployment

Component Libraries for Svelte

Primary Options

  1. Skeleton UI (Recommended)

    • Tailwind-based
    • Comprehensive component set
    • Good documentation
    • Mobile-friendly
  2. Carbon Components Svelte

    • IBM's design system
    • Enterprise-grade
    • Accessibility built-in
    • More complex
  3. Svelte Material UI

    • Material Design
    • Familiar patterns
    • Good for mobile
    • Heavier bundle
  4. Build Custom with Tailwind UI

    • Purchase Tailwind UI ($299)
    • Convert components to Svelte
    • Full control
    • Best performance

Recommendation

Start with Skeleton UI for rapid development, consider custom components for production.


Forms and Validation

Form Handling

<script>
import { createForm } from 'svelte-forms-lib'
import * as zod from 'zod'

const schema = zod.object({
name: zod.string().min(1),
email: zod.string().email(),
budget: zod.number().min(0)
})

const { form, errors, handleSubmit } = createForm({
initialValues: { name: '', email: '', budget: 0 },
validationSchema: schema,
onSubmit: values => {
$inertia.post('/projects', values)
}
})
</script>

<form on:submit={handleSubmit}>
<input bind:value={$form.name} />
{#if $errors.name}<span>{$errors.name}</span>{/if}

<button type="submit">Save Project</button>
</form>

State Management

Svelte Stores (Built-in)

// stores/user.js
import { writable } from 'svelte/store'

export const currentUser = writable(null)
export const company = writable(null)

// Using in components
import { currentUser } from '$stores/user'

$currentUser = { name: 'John', role: 'admin' }

Why Not Redux/Pinia?

Svelte's built-in stores are sufficient for PBS's needs:

  • Simple API
  • No boilerplate
  • Reactive by default
  • TypeScript support

Mobile Considerations

Crew Portal (Mobile-First)

<!-- Responsive by default with Tailwind -->
<div class="px-4 md:px-6 lg:px-8">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{#each projects as project}
<ProjectCard {project} />
{/each}
</div>
</div>

Touch Interactions

<script>
import { swipe } from 'svelte-gestures'
</script>

<div
use:swipe
on:swipe={(e) => handleSwipe(e.detail.direction)}
class="touch-pan-y"
>
<!-- Swipeable content -->
</div>

Development Workflow

Project Setup

# 1. Create Laravel project
composer create-project laravel/laravel pbs-planning
cd pbs-planning

# 2. Install Inertia server-side
composer require inertiajs/inertia-laravel

# 3. Install frontend dependencies
npm install svelte @inertiajs/svelte @sveltejs/vite-plugin-svelte
npm install -D tailwindcss @tailwindcss/forms

# 4. Configure Vite for Svelte
# vite.config.js
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
plugins: [
laravel({
input: 'resources/js/app.js',
refresh: true,
}),
svelte(),
],
})

Development Commands

# Start Laravel
php artisan serve

# Start Vite dev server (separate terminal)
npm run dev

# Run tests
php artisan test # Backend
npm run test # Frontend

# Build for production
npm run build
php artisan optimize

Testing Strategy

Frontend (Svelte)

// ProjectList.test.js
import { render, fireEvent } from '@testing-library/svelte'
import ProjectList from './ProjectList.svelte'

test('displays projects', () => {
const { getByText } = render(ProjectList, {
props: {
projects: [{ id: 1, name: 'Test Project' }]
}
})

expect(getByText('Test Project')).toBeInTheDocument()
})

E2E Testing

// playwright.test.js
test('create new project', async ({ page }) => {
await page.goto('/projects/create')
await page.fill('input[name="name"]', 'New Project')
await page.click('button[type="submit"]')
await expect(page).toHaveURL('/projects')
})

Deployment

  • Backend: DigitalOcean App Platform or Hetzner Cloud
  • Database: Managed PostgreSQL (same provider)
  • Redis: Managed Redis or same server
  • Files: S3-compatible storage (Spaces/Hetzner)

Deployment Process

# Build assets
npm run build

# Deploy with Laravel Forge
# Or use Docker:
docker build -t pbs-app .
docker push registry.example.com/pbs-app

Migration Path from Prototypes

Your existing HTML prototypes require minimal changes:

Current Prototype

<div class="project-card">
<h3>Project Name</h3>
<button onclick="editProject(1)">Edit</button>
</div>

Svelte Version

<div class="project-card">
<h3>{project.name}</h3>
<button on:click={() => editProject(project.id)}>Edit</button>
</div>

Changes needed:

  • Add {} for interpolation
  • Change onclick to on:click
  • Add bind: for two-way binding
  • Wrap in <script> tags

Fallback Plan: Switching to React

If Svelte doesn't work out, switching to React is straightforward:

  1. Same Backend: Laravel + Inertia unchanged
  2. Replace Svelte adapter: @inertiajs/react
  3. Convert components: Svelte → React syntax
  4. Same Tailwind styles: No CSS changes

Time to switch: ~1-2 weeks for MVP features

What Changes

<!-- Svelte -->
<script>
export let project
</script>
<div>{project.name}</div>
// React
export default function Component({ project }) {
return <div>{project.name}</div>
}

What Stays Same

  • Laravel controllers
  • Database/migrations
  • Validation logic
  • Tailwind styles
  • Inertia routing

Cost Analysis

Open Source (Free)

  • ✅ Laravel
  • ✅ Svelte
  • ✅ Inertia.js
  • ✅ Tailwind CSS
  • ✅ All core libraries

Optional Paid

  • Tailwind UI: $299 (one-time)
  • Skeleton UI Pro: $149 (one-time)
  • Laravel Forge: $19/month
  • Hosting: $40-100/month

Total MVP Cost: ~$40-100/month (hosting only)


Risk Assessment

Svelte Risks

RiskMitigation
Smaller ecosystemUse Tailwind for UI components
Fewer Svelte developersEasy to learn (1-2 days from React)
Less enterprise adoptionGrowing rapidly, fallback to React
Component library optionsSkeleton UI or build custom

Why Risks Are Acceptable

  1. PBS is not complex enough to need massive ecosystem
  2. Core team can learn Svelte quickly
  3. Fallback to React is straightforward
  4. Performance benefits outweigh ecosystem size

Implementation Timeline

Phase 0: Foundation (Week 1)

  • Framework decision (Svelte)
  • Development environment setup
  • Laravel + Inertia + Svelte scaffold
  • Database setup (PostgreSQL)
  • Tailwind configuration

Phase 1: Auth & Structure (Week 2)

  • Laravel Sanctum setup
  • Login/Register pages (Svelte)
  • Multi-tenancy configuration
  • Role-based permissions

Phase 2: Core Features (Weeks 3-4)

  • Projects CRUD
  • Staff management
  • Client management
  • Basic navigation

Phase 3: Booking System (Weeks 5-6)

  • Offer creation
  • Assignment workflow
  • Availability checking
  • Calendar view

Phase 4: Crew Portal (Weeks 7-8)

  • Crew registration
  • Offer responses
  • Availability management
  • Mobile optimization

Decision Summary

We choose Svelte because:

  1. Simplest code - No hooks, no .value, just JavaScript
  2. Best performance - Critical for mobile crew portal
  3. Fastest forms - 40% less code than React for forms
  4. Transparent compilation - Easier debugging
  5. High developer satisfaction - 89% vs React's 43%

Fallback plan exists:

  • Switch to React if needed (1-2 weeks)
  • Same Laravel backend
  • Same Inertia.js architecture
  • Same Tailwind styles

Next Step: Initialize project and build first feature (Project List) as proof of concept.


This document represents our current technology decision. It will be updated as we gain experience with the stack.