Sitemap
Peter Amajor

Peter Amajor

Email & direct-response copywriter + Laravel/mobile developer. I write words that sell and build apps that ship. Abuja, Nigeria. Open to remote work.

Waiting Feels Longer When Nothing Moves

Building streaming replies into Blank AI with Laravel and Livewire taught me that speed is partly something you show, not just something you measure.

4 min readOct 21, 2026

--

You type a question into a chat app, press Enter, and nothing happens. One second, two, three. You start wondering if it froze, and your thumb is already heading for refresh. Then a full wall of text lands on the screen at once.

The answer might be great. By then you don’t really care, because for those few seconds the app gave you nothing. Same question, same answer, same total time, but if the first words had shown up straight away and the rest had followed as they were written, it would have felt like a completely different product.

I spent a lot of time on that gap while building Blank AI, a chat app I made with Laravel and Livewire. It streams its replies. Getting the streaming right taught me more about perceived speed than any performance article I’d read before it.

The number people feel

When developers talk about speed, we usually mean total time. How long until the request is done?

In a chat interface, the number users actually feel is how long until the first word appears. Language models produce text piece by piece. If you hold everything back until the whole response is ready, you’ve thrown away the one thing that makes the wait bearable.

Streaming doesn’t make the model any faster. It just lets people see that something is happening, and that turns out to matter a lot.

You see the same idea outside AI too. Upload bars, skeleton screens, a button that switches to “Saving…” the moment you tap it. People will put up with a wait. A blank screen with no feedback is what makes them leave.

Livewire makes the first step easy

Part of why I like Livewire is that I can build interactive stuff while staying in PHP and Blade for most of it. Streaming is a good example.

Livewire 3 has a wire:stream directive for the view and a stream() method in the component. As chunks of the reply come back from the model, you push each one to a named target on the page, and the browser shows it while the request is still running. No separate JavaScript front end. No socket server you wrote yourself at 2 AM.

That part took me an afternoon. Everything around it took longer.

The pipe has to stay open all the way

This is the bit that catches people. Your code can stream perfectly and the user still gets the wall of text.

Get Peter Amajor’s stories in your inbox

Join Medium for free to get updates from this writer.

Between Laravel and the browser there are layers that like to collect output before passing it along: PHP’s own output buffering, a reverse proxy like Nginx, compression. If any of them sits on your chunks, the streaming is happening on your server and nowhere else.

So don’t call it done because it works locally. Test it somewhere that looks like production, with the same proxy and hosting setup, and watch it arrive in a real browser. If the words come in bursts, or all at once, something in the middle is buffering.

Half-finished text is its own state

A normal request is either waiting or done. A streamed reply has a third state, which is half-written, and that raises questions I hadn’t thought about before.

When do you save the message? Normally once it’s complete, not chunk by chunk. Fine. But what if the provider errors out halfway, or the user closes the tab? Do you keep the partial answer and mark it as cut off, or throw it away and offer a retry? I don’t think there’s one right answer. There should be a deliberate one, though, and not whatever happens by accident.

Formatting is the other headache. Models love Markdown. Halfway through a reply you can have an opening code fence with no closing one, or half a list. Render Markdown on every chunk and the layout can jump around in ugly ways. Better to decide how your app handles that before a user finds it for you.

Small things that help

A few smaller touches mattered nearly as much as the streaming itself.

Show the user’s own message the instant they send it. Making them wait for a round trip before their own words appear feels strangely rude.

Guard against double sends while a reply is in progress. Nervous users hit Enter twice, and your API bill shouldn’t pay for their nerves.

Keep the scroll behaving. As new text comes in, the view should follow along, unless the user has scrolled up to reread something. Dragging them back down mid-sentence is annoying in a way people notice even if they can’t name it.

What I took away

Building Blank AI convinced me that speed is partly a design choice. You can’t always make the server faster, especially when the slow part is a model running on someone else’s infrastructure. You can nearly always make the wait feel shorter by showing progress early and handling the in-between states properly.

I ask a different question about features now. Not only “how long does this take?” but also “what is the user looking at while it happens?”

If you’re adding AI to a Laravel app

I’m Peter Amajor, a full-stack Laravel developer based in Abuja, Nigeria, and I take on paid remote work. I build with Laravel, Livewire and Alpine.js, ship PWAs that live on people’s phones, and I’m comfortable wiring AI features like chat and streaming replies into real products.

If you’ve got a Laravel app that could use an AI feature, or one that works but feels slow to the people using it, I’d be glad to talk. Email me at mrpeteramajor@gmail.com.

--

--

Peter Amajor
Peter Amajor

Written by Peter Amajor

Email & direct-response copywriter + Laravel/mobile developer. I write words that sell and build apps that ship. Abuja, Nigeria. Open to remote work.