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