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
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Backend Framework | Laravel | 11.x | PHP framework, API, business logic |
| Frontend Framework | Svelte | 5.x | UI components and interactivity |
| Frontend Bridge | Inertia.js | 1.0+ | Connects Laravel to Svelte without API |
| CSS Framework | Tailwind CSS | 3.4+ | Utility-first styling |
| Database | PostgreSQL | 15+ | Primary data storage |
| Cache/Queue | Redis | 7.0+ | Session, cache, job queues |
Development Tools
| Tool | Technology | Purpose |
|---|---|---|
| Build Tool | Vite | Fast development and building |
| Package Manager | npm/pnpm | JavaScript dependencies |
| PHP Dependencies | Composer | PHP package management |
| Code Formatting | Prettier + ESLint | JavaScript formatting |
| PHP Formatting | Laravel Pint | PHP code style |
| Testing (Frontend) | Vitest + Playwright | Unit and E2E tests |
| Testing (Backend) | Pest PHP | Laravel 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
-
No REST API Needed
// Laravel Controller
public function index()
{
return Inertia::render('Projects/Index', [
'projects' => Project::where('company_id', $companyId)
->with('client')
->paginate(20)
]);
} -
Svelte Receives Props Directly
<!-- Projects/Index.svelte -->
<script>
export let projects = []
function deleteProject(id) {
$inertia.delete(`/projects/${id}`)
}
</script> -
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
-
Skeleton UI (Recommended)
- Tailwind-based
- Comprehensive component set
- Good documentation
- Mobile-friendly
-
Carbon Components Svelte
- IBM's design system
- Enterprise-grade
- Accessibility built-in
- More complex
-
Svelte Material UI
- Material Design
- Familiar patterns
- Good for mobile
- Heavier bundle
-
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
Recommended Hosting
- 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
onclicktoon: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:
- Same Backend: Laravel + Inertia unchanged
- Replace Svelte adapter:
@inertiajs/react - Convert components: Svelte → React syntax
- 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
| Risk | Mitigation |
|---|---|
| Smaller ecosystem | Use Tailwind for UI components |
| Fewer Svelte developers | Easy to learn (1-2 days from React) |
| Less enterprise adoption | Growing rapidly, fallback to React |
| Component library options | Skeleton UI or build custom |
Why Risks Are Acceptable
- PBS is not complex enough to need massive ecosystem
- Core team can learn Svelte quickly
- Fallback to React is straightforward
- 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:
- Simplest code - No hooks, no
.value, just JavaScript - Best performance - Critical for mobile crew portal
- Fastest forms - 40% less code than React for forms
- Transparent compilation - Easier debugging
- 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.