Tools: I Built a Job Platform With Laravel 13, Livewire 4, and GPT-5 — Here's What I Learned

Tools: I Built a Job Platform With Laravel 13, Livewire 4, and GPT-5 — Here's What I Learned

Why TALL Stack in 2026?

The Architecture

How GPT-5 Fits Into a Laravel App

1. SEO Meta Generation

2. Smart Internal Linking

3. Content Analysis

Redis: The Swiss Army Knife

Nginx: More Than Just a Reverse Proxy

The Frontend: No Build Step (Almost)

Notifications: Telegram First

Testing and DevOps

Lessons Learned

What's Next I'm building UWork.kz — a local services marketplace in Kazakhstan. The stack: Laravel 13 + Livewire 4 + Alpine.js + Tailwind CSS (the TALL stack), with GPT-5 handling SEO content and smart internal linking. In this post I'll break down the architecture decisions, the trade-offs, and the things that surprised me along the way. Every second dev.to post right now is about Next.js, Nuxt, or some React meta-framework. Fair enough — they're great tools. But when you're a solo developer building a marketplace for a specific regional market, you need to ship fast and maintain easily.

Here's why I went with TALL:One language, one mental model. With Livewire 4, my reactive components are just PHP classes. No context-switching between a REST API and a React frontend. No state management library. No hydration headaches. I write a PHP class, drop a Blade component, and it just works.Filament 5 for admin. Building a custom admin panel from scratch is a time sink. Filament gave me a fully functional back-office in days — user management, content moderation, analytics dashboards — all with minimal custom code.Alpine.js for the small stuff. Modals, dropdowns, mobile menus, form validation feedback — Alpine handles these without pulling in a 50kb JavaScript framework. Here's a simplified view of the stack:┌──────────────────────────────────┐│ Nginx ││ SSL · FastCGI Cache · GeoIP │└──────────────┬───────────────────┘ │┌──────────────▼───────────────────┐│ PHP 8.5 FPM ││ Laravel 13 Application ││ ││ ┌───────────┐ ┌─────────────┐ ││ │ Livewire 4│ │ Filament 5 │ ││ │ (Frontend)│ │ (Admin) │ ││ └───────────┘ └─────────────┘ ││ ││ ┌───────────┐ ┌─────────────┐ ││ │ Eloquent │ │ Horizon │ ││ │ (ORM) │ │ (Queues) │ ││ └─────┬─────┘ └──────┬──────┘ │└────────┼────────────────┼────────┘ │ │ ┌────▼────┐ ┌─────▼─────┐ │ MySQL │ │ Redis │ │ 8.4 │ │Cache/Queue│ └─────────┘ └───────────┘Everything runs in Docker Compose — one docker compose up and you have the full environment. Same config for dev and prod, just different .env values. This is where it gets interesting. I use the OpenAI API for three things: Every service listing needs a unique meta title and description. Writing these manually for thousands of listings? Not happening. I have a queued job that generates SEO-optimized meta tags: GPT analyzes page content and suggests relevant internal links. This boosted our internal link density significantly — something that's hard to do manually at scale. New listings go through a content quality check. The AI flags low-effort descriptions, duplicate content, and suggests improvements to the service provider.The key insight: Don't call the API synchronously. Everything goes through Laravel's queue system with Redis as the driver. Horizon monitors the queues, and if something fails, it retries with exponential backoff. Redis does a lot of heavy lifting in this stack: That last one is worth explaining. Instead of running an UPDATE query on every page view, I increment a Redis counter. A scheduled command runs every 5 minutes, reads the counters, batch-updates MySQL, and resets them. This pattern reduced write load on the database dramatically.`// On page viewRedis::hincrby("listing:views:{$date}", $listingId, 1); // Scheduled flush (every 5 min)$views = Redis::hgetall("listing:views:{$date}");foreach (array_chunk($views, 500, true) as $batch) { foreach ($batch as $id => $count) { Listing::where('id', $id) ->increment('daily_views', (int) $count); }}Redis::del("listing:views:{$date}");` Our Nginx config does quite a bit: The FastCGI cache alone cut TTFB from ~200ms to ~15ms for cached pages. For a job platform where most visitors are anonymous searchers, this is a massive win. One thing I love about the TALL stack is the simplicity of the frontend: We use Vite 8 (with the Rolldown bundler) only to compile Tailwind and handle asset versioning. There's almost no custom JavaScript. The entire frontend is Blade templates + Livewire components + Alpine directives.For example, our real-time search:<div x-data="{ open: false }"> <input wire:model.live.debounce.300ms="search" @focus="open = true" @click.away="open = false" placeholder="Search services..." > <div x-show="open" x-transition> @foreach($results as $result) <a href="{{ $result->url }}"> {{ $result->title }} </a> @endforeach </div></div>No REST endpoint. No fetch(). No state management. Livewire handles the server communication, Alpine handles the dropdown visibility. Clean and simple. Instead of building a custom notification system, we went Telegram-first: Why? Our target audience (service providers in Kazakhstan) lives in Telegram. Email open rates here are around 15%. Telegram message read rates? Over 90%.We also support Web Push for browser notifications as a fallback, but Telegram is the primary channel. The testing and deployment setup: If you're building something similar or have questions about the TALL stack in production, drop a comment. Happy to share more details.Check out the platform: UWork.kz — a local services marketplace connecting people with trusted professionals in Kazakhstan.

If you found this useful, follow me for more posts about Laravel, AI integration, and building products for emerging markets. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ class GenerateListingSeo implements ShouldQueue { public function handle(OpenAIService $ai): void { $prompt = "Write a meta title (max 60 chars) and meta description (max 155 chars) for a -weight: 500;">service listing: {$this->listing->title} in {$this->listing->city}. Language: Russian."; $result = $ai->complete($prompt); $this->listing->-weight: 500;">update([ 'meta_title' => $result['title'], 'meta_description' => $result['description'], ]); } } class GenerateListingSeo implements ShouldQueue { public function handle(OpenAIService $ai): void { $prompt = "Write a meta title (max 60 chars) and meta description (max 155 chars) for a -weight: 500;">service listing: {$this->listing->title} in {$this->listing->city}. Language: Russian."; $result = $ai->complete($prompt); $this->listing->-weight: 500;">update([ 'meta_title' => $result['title'], 'meta_description' => $result['description'], ]); } } class GenerateListingSeo implements ShouldQueue { public function handle(OpenAIService $ai): void { $prompt = "Write a meta title (max 60 chars) and meta description (max 155 chars) for a -weight: 500;">service listing: {$this->listing->title} in {$this->listing->city}. Language: Russian."; $result = $ai->complete($prompt); $this->listing->-weight: 500;">update([ 'meta_title' => $result['title'], 'meta_description' => $result['description'], ]); } } - Cache: Query results, computed statistics, category trees - Queues: All async jobs (emails, SEO generation, notifications) - Sessions: User session storage - Rate limiting: API and bot protection - Stats buffer: Real-time counters (views, clicks) that flush to MySQL periodically - FastCGI caching for anonymous users — category pages and landing pages serve from cache, bypassing PHP entirely - GeoIP filtering — the platform targets Kazakhstan, so we handle geo-specific logic at the Nginx level - SSL termination — Let's Encrypt with auto-renewal - Tailwind CSS v4 + DaisyUI v5 for design system components - Livewire 4 for reactive server-side components (search, filters, forms) - Alpine.js v3 for client-side interactions - New order notifications → Telegram bot - Verification codes → Telegram - Admin alerts → Telegram channel - PHPUnit 12 for unit and integration tests - Laravel Pint for code style (runs in CI) - k6 for load testing before releases - Docker Compose handles everything — no Kubernetes, no AWS ECS, no complexity tax I know this is controversial in 2026, but Docker Compose on a decent VPS handles a lot more traffic than people think. We're not Netflix. We're a regional marketplace. A single well-configured server with proper caching handles our load just fine. - TALL stack is underrated for MVPs. The developer experience is incredible when you don't have to maintain two separate codebases (API + SPA). - AI-generated content needs guardrails. GPT-5 is amazing, but you need validation layers. We check every generated meta tag for length, language, and relevance before saving. - Redis is worth learning deeply. Most tutorials cover basic caching. But using Redis for stats buffering, rate limiting, and queue management transforms your app's performance. - Ship for your audience, not for Hacker News. Telegram notifications > email. Russian-language SEO > English best practices. GeoIP filtering > global CDN. Know your users. - Docker Compose is fine. Seriously. If your app fits on one server (and most apps do), skip the orchestration complexity. - A recommendation engine for matching users with -weight: 500;">service providers - Real-time chat between clients and providers (Livewire + WebSockets) - Mobile app via a PWA approach