<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Лаврентий Солдатенко</title>
    <description>The latest articles on DEV Community by Лаврентий Солдатенко (@__f238).</description>
    <link>https://dev.to/__f238</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3882368%2Fb1c6e4e8-0e77-4437-a980-3db737640839.jpg</url>
      <title>DEV Community: Лаврентий Солдатенко</title>
      <link>https://dev.to/__f238</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/__f238"/>
    <language>en</language>
    <item>
      <title>I Built a Job Platform With Laravel 13, Livewire 4, and GPT-5 — Here's What I Learned</title>
      <dc:creator>Лаврентий Солдатенко</dc:creator>
      <pubDate>Thu, 16 Apr 2026 11:49:00 +0000</pubDate>
      <link>https://dev.to/__f238/i-built-a-job-platform-with-laravel-13-livewire-4-and-gpt-5-heres-what-i-learned-3a9g</link>
      <guid>https://dev.to/__f238/i-built-a-job-platform-with-laravel-13-livewire-4-and-gpt-5-heres-what-i-learned-3a9g</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I'm building &lt;a href="https://uwork.kz/" rel="noopener noreferrer"&gt;UWork.kz&lt;/a&gt; — 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why TALL Stack in 2026?
&lt;/h2&gt;

&lt;p&gt;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.&lt;br&gt;
Here's why I went with TALL:&lt;br&gt;
&lt;strong&gt;One language, one mental model&lt;/strong&gt;. 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.&lt;br&gt;
&lt;strong&gt;Filament 5 for admin&lt;/strong&gt;. 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.&lt;br&gt;
&lt;strong&gt;Alpine.js&lt;/strong&gt; for the small stuff. Modals, dropdowns, mobile menus, form validation feedback — Alpine handles these without pulling in a 50kb JavaScript framework.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Here's a simplified view of the stack:&lt;br&gt;
&lt;code&gt;┌──────────────────────────────────┐&lt;br&gt;
│           Nginx                  │&lt;br&gt;
│  SSL · FastCGI Cache · GeoIP    │&lt;br&gt;
└──────────────┬───────────────────┘&lt;br&gt;
               │&lt;br&gt;
┌──────────────▼───────────────────┐&lt;br&gt;
│         PHP 8.5 FPM              │&lt;br&gt;
│      Laravel 13 Application     │&lt;br&gt;
│                                  │&lt;br&gt;
│  ┌───────────┐  ┌─────────────┐ │&lt;br&gt;
│  │ Livewire 4│  │ Filament 5  │ │&lt;br&gt;
│  │ (Frontend)│  │   (Admin)   │ │&lt;br&gt;
│  └───────────┘  └─────────────┘ │&lt;br&gt;
│                                  │&lt;br&gt;
│  ┌───────────┐  ┌─────────────┐ │&lt;br&gt;
│  │  Eloquent │  │   Horizon   │ │&lt;br&gt;
│  │   (ORM)   │  │  (Queues)   │ │&lt;br&gt;
│  └─────┬─────┘  └──────┬──────┘ │&lt;br&gt;
└────────┼────────────────┼────────┘&lt;br&gt;
         │                │&lt;br&gt;
    ┌────▼────┐     ┌─────▼─────┐&lt;br&gt;
    │ MySQL   │     │   Redis   │&lt;br&gt;
    │  8.4    │     │Cache/Queue│&lt;br&gt;
    └─────────┘     └───────────┘&lt;/code&gt;&lt;br&gt;
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.&lt;/p&gt;
&lt;h2&gt;
  
  
  How GPT-5 Fits Into a Laravel App
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. I use the OpenAI API for three things:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. SEO Meta Generation
&lt;/h3&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateListingSeo&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShouldQueue&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;OpenAIService&lt;/span&gt; &lt;span class="nv"&gt;$ai&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Write a meta title (max 60 chars) and 
                   meta description (max 155 chars) for a 
                   service listing: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;listing&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 
                   in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;listing&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. 
                   Language: Russian."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$ai&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;listing&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'meta_title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'meta_description'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Smart Internal Linking
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Content Analysis
&lt;/h3&gt;

&lt;p&gt;New listings go through a content quality check. The AI flags low-effort descriptions, duplicate content, and suggests improvements to the service provider.&lt;br&gt;
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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redis: The Swiss Army Knife
&lt;/h2&gt;

&lt;p&gt;Redis does a lot of heavy lifting in this stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache&lt;/strong&gt;: Query results, computed statistics, category trees&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queues&lt;/strong&gt;: All async jobs (emails, SEO generation, notifications)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sessions&lt;/strong&gt;: User session storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt;: API and bot protection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stats buffer&lt;/strong&gt;: Real-time counters (views, clicks) that flush to MySQL periodically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;br&gt;
`// On page view&lt;br&gt;
Redis::hincrby("listing:views:{$date}", $listingId, 1);&lt;/p&gt;

&lt;p&gt;// Scheduled flush (every 5 min)&lt;br&gt;
$views = Redis::hgetall("listing:views:{$date}");&lt;br&gt;
foreach (array_chunk($views, 500, true) as $batch) {&lt;br&gt;
    foreach ($batch as $id =&amp;gt; $count) {&lt;br&gt;
        Listing::where('id', $id)&lt;br&gt;
            -&amp;gt;increment('daily_views', (int) $count);&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
Redis::del("listing:views:{$date}");`&lt;/p&gt;

&lt;h2&gt;
  
  
  Nginx: More Than Just a Reverse Proxy
&lt;/h2&gt;

&lt;p&gt;Our Nginx config does quite a bit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FastCGI caching for anonymous users — category pages and landing pages serve from cache, bypassing PHP entirely&lt;/li&gt;
&lt;li&gt;GeoIP filtering — the platform targets Kazakhstan, so we handle geo-specific logic at the Nginx level&lt;/li&gt;
&lt;li&gt;SSL termination — Let's Encrypt with auto-renewal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Frontend: No Build Step (Almost)
&lt;/h2&gt;

&lt;p&gt;One thing I love about the TALL stack is the simplicity of the frontend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tailwind CSS v4 + DaisyUI v5 for design system components&lt;/li&gt;
&lt;li&gt;Livewire 4 for reactive server-side components (search, filters, forms)&lt;/li&gt;
&lt;li&gt;Alpine.js v3 for client-side interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;br&gt;
For example, our real-time search:&lt;br&gt;
&lt;code&gt;&amp;lt;div x-data="{ open: false }"&amp;gt;&lt;br&gt;
    &amp;lt;input &lt;br&gt;
        wire:model.live.debounce.300ms="search" &lt;br&gt;
        @focus="open = true"&lt;br&gt;
        @click.away="open = false"&lt;br&gt;
        placeholder="Search services..."&lt;br&gt;
    &amp;gt;&lt;br&gt;
    &amp;lt;div x-show="open" x-transition&amp;gt;&lt;br&gt;
        @foreach($results as $result)&lt;br&gt;
            &amp;lt;a href="{{ $result-&amp;gt;url }}"&amp;gt;&lt;br&gt;
                {{ $result-&amp;gt;title }}&lt;br&gt;
            &amp;lt;/a&amp;gt;&lt;br&gt;
        @endforeach&lt;br&gt;
    &amp;lt;/div&amp;gt;&lt;br&gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;br&gt;
No REST endpoint. No fetch(). No state management. Livewire handles the server communication, Alpine handles the dropdown visibility. Clean and simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notifications: Telegram First
&lt;/h2&gt;

&lt;p&gt;Instead of building a custom notification system, we went Telegram-first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New order notifications → Telegram bot&lt;/li&gt;
&lt;li&gt;Verification codes → Telegram&lt;/li&gt;
&lt;li&gt;Admin alerts → Telegram channel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why? Our target audience (service providers in Kazakhstan) lives in Telegram. Email open rates here are around 15%. Telegram message read rates? Over 90%.&lt;br&gt;
We also support Web Push for browser notifications as a fallback, but Telegram is the primary channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing and DevOps
&lt;/h2&gt;

&lt;p&gt;The testing and deployment setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHPUnit 12 for unit and integration tests&lt;/li&gt;
&lt;li&gt;Laravel Pint for code style (runs in CI)&lt;/li&gt;
&lt;li&gt;k6 for load testing before releases&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;TALL stack is underrated for MVPs&lt;/strong&gt;. The developer experience is incredible when you don't have to maintain two separate codebases (API + SPA).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-generated content needs guardrails&lt;/strong&gt;. GPT-5 is amazing, but you need validation layers. We check every generated meta tag for length, language, and relevance before saving.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis is worth learning deeply&lt;/strong&gt;. Most tutorials cover basic caching. But using Redis for stats buffering, rate limiting, and queue management transforms your app's performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship for your audience, not for Hacker News&lt;/strong&gt;. Telegram notifications &amp;gt; email. Russian-language SEO &amp;gt; English best practices. GeoIP filtering &amp;gt; global CDN. Know your users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Compose is fine. Seriously&lt;/strong&gt;. If your app fits on one server (and most apps do), skip the orchestration complexity.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We're working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A recommendation engine for matching users with service providers&lt;/li&gt;
&lt;li&gt;Real-time chat between clients and providers (Livewire + WebSockets)&lt;/li&gt;
&lt;li&gt;Mobile app via a PWA approach&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building something similar or have questions about the TALL stack in production, drop a comment. Happy to share more details.&lt;br&gt;
Check out the platform: &lt;a href="https://uwork.kz/" rel="noopener noreferrer"&gt;UWork.kz&lt;/a&gt; — a local services marketplace connecting people with trusted professionals in Kazakhstan.&lt;br&gt;
&lt;em&gt;If you found this useful, follow me for more posts about Laravel, AI integration, and building products for emerging markets.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
