Progress bar for background tasks on Laravel and React

A progress indicator that diverges from the queue undermines trust in the product and damages the relationship with the client. We build reliable progress bars turnkey: from queue audit to handing over the guidelines to your team. We reduce the share of false statuses and the number of support requests severalfold.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Frequently Asked Questions

Why the progress bar for background tasks diverges from reality

A task goes into the task queue, a worker picks it up, and the user watches the bar.

The trouble is that the completion percentage is calculated not from the work itself, but alongside it: the handler writes "started" and "finished," and between those two records the bar sits at zero for all twenty minutes.

If progress does move, it lies differently — it counts records rather than their weight: a thousand small rows fly by in a second, while one heavy operation holds the bar at 97% for as long as the entire rest of the batch.

The second reason is that status updates live in a separate transaction from the useful work.

If a worker crashes on a timeout or runs out of memory, the record remains in the "running" state, and it is impossible to distinguish a stuck task from merely a long one from the interface.

The user waits, refreshes the page, starts it again — and gets duplicates: the same email, the same invoice, the same export arrive twice, while the queue accumulates extra jobs.

The cost of such a failure is not a crooked bar, but distrust of any long-running action: people start mashing the button, support sorts out duplicates, and the administrator does not understand what is actually happening in the queue.

Below we break down how this is closed — from where progress is stored and who writes it, to how to distinguish a failed worker from a working one.

What a reliable status indicator for long-running tasks provides

Status reliability is a contract between the Laravel queue worker and the interface, not a design element.

When the progress bar shows the real share of what has been completed rather than an endless "loading," the user stops restarting the operation: repeated runs drop severalfold, because no one mashes the button again on the thirtieth second of waiting.

The second effect is the removal of the failure class "task without status." If the worker crashes, the record hangs in the "in progress" state indefinitely, and support manually finds out what happened.

Periodic reconciliation of the task log with the queue and moving stuck processes to an explicit status with a reason close this scenario: the task either completes or honestly reports an error and remains available for retry.

The load on support falls along with the uncertainty. Requests like "is it still running?" disappear when the stage and a predictable remaining time are visible on the screen, and specialists deal with real failures instead of clarifying status.

Waiting time becomes predictable: the bar is built from the processed share of tasks, not from an averaged estimate, and is smoothed so it does not jump.

Long-running operations can be started in the background and you can leave the page — upon return, the status is restored from the saved progress.

Progress bar display modes: comparison of approaches

A task queue on Laravel and an interface on React are different processes: one side counts the steps, the other knows nothing about them.

The state transfer mode is chosen based on two questions: can the amount of work be calculated in advance, and how quickly the client needs an update. This determines whether we show honest percentages or an indeterminate indicator.

A deterministic counter works when the task is split into chunks and after each chunk the record "processed N of M" is updated. Then the scale reflects the real progress of execution, not server activity.

If the work cannot be split — for example, there is one long call inside — an indeterminate indicator is more correct: it does not promise "two minutes remaining" that cannot be substantiated.

Option When it fits Limitations
Periodic polling The task runs for minutes, a delay of a few seconds is acceptable Accuracy is limited by the polling interval; each client adds requests to the state store
Event stream Real execution progress is needed: percentages, steps, log Holds long-lived connections; if it breaks, the state must be re-requested
Bidirectional channel In addition to progress, commands are needed: pause, cancel, retry More expensive to maintain; excessive if data only goes to the client

In practice, a combination is used: periodic polling for the task list, an event stream for the open task card. If the connection breaks, the interface falls back to polling and re-requests the state — the bar does not get stuck on the last value.

Changing the mode does not require reworking the task itself: the counter is written to the state store, and the delivery method to the interface changes separately.

Implementation stages: from queue audit to handover of the regulations

Progress is not an animation on the screen, but a reflection of the real state of the task in the store. Therefore, the order of work begins with analyzing what happens on the server and ends with regulations that the team can then follow on its own.

  1. Queue audit. We analyze the lifecycle of a background task: what workers exist, how many steps are inside each, where a task can be restarted, canceled, or discarded on timeout. The result is a transition map on which everything else is built.
  2. Status schema. We design a finite set of states (queued, running, step N of M, done, error) and the transition rules between them. We separately fix the behavior on re-run — otherwise the user will see two progress bars for one task.
  3. Step boundary. We agree on what counts as a step: a unit of work that is visible to a person. A step that is too large produces a "frozen" bar; one that is too small produces extra records in the store and increased load on it.
  4. State store. We choose where progress lives: cache, database table, or message queue. We set the write frequency and record lifetime so that data does not accumulate after the task completes.
  5. Delivery to the interface. We determine the update channel — periodic polling or a streaming connection — as well as state restoration on page reload and behavior on connection loss. The client must not see zero progress on an already completed task.
  6. Load testing. We run several tasks at the same time and see whether response time grows, whether updates are lost, and whether the final status matches the actual processing result.
  7. Handover of regulations. We hand over the status schema, store configuration, rules for adding new steps, and analysis of typical failures — from this point on, the team extends progress without our involvement.

Load testing and handover of regulations are not a formality, but the points where discrepancies most often surface. We close them before delivery, so after launch progress does not diverge from reality on the very first worker failure.

Configurations, schemas, and regulations that remain with the team

A progress bar stops being decoration when it is backed by a described task lifecycle: who submitted it, which queue it went to, what counts as completion, and what to do if it hangs.

Therefore, the handover package includes not only working code but also a set of artifacts that the customer's team can use to manage such tasks without us.

  • Queue configuration — which queues are set up, which task types go to which, how many workers per queue, and how priorities and retries are configured.
  • Lifecycle schema — all task statuses from submission to result or error, indicating which transitions are allowed and which status the user sees in the progress bar.
  • Status access policies — which roles can start, stop, and retry a task, who sees only their own progress, and who sees a summary across all.
  • Support instructions — typical situations: a task is stalled, broke off midway, ran twice; what to check first and which logs to look at.
  • Rollback procedure — how to return the previous processing behavior if the change interferes, and which data is not lost in the process.
  • Retry regulations — the conditions under which a task can be safely restarted so as not to get duplicate results.
  • Observability — which metrics on queue length and processing time are collected, and at which thresholds alerts fire.

Each artifact is designed so that it can be read without us: a schema with labels, an instruction with specific steps, a configuration with comments on non-obvious values.

Case study: indicator and queue discrepancy eliminated in two weeks

Before: the indicator froze, and tasks were lost silently

A bulk export of reports with several thousand rows went through the queue, but the on-screen indicator counted progress by a counter that was updated only after each batch completed successfully.

If the worker crashed midway — for example, on an unexpected date format in one of the files — outwardly nothing changed: the bar froze at 60–70%, the person waited, and the task was already dead.

We analyzed incidents over a month: some exports did not reach the end, and none of these failures produced an explicit error in the interface.

After: state is reconciled with fact, not expectation

The source of truth was made the server side on Laravel: each task received a step log, the progress bar draws what has actually been completed, reading state from the store rather than from the worker's memory.

A crash became an event — a failed attempt is recorded, the task is moved to a failure status, the remainder is recalculated from scratch, and the user sees the reason and a retry button.

State reconciliation was moved to a background check: it periodically compares the queue, the log, and what is shown in the browser, and pulls in discrepancies.

Implementation metrics over two weeks: discrepancies between the indicator and fact — zero, silent worker crashes — zero, exports abandoned midway — zero. The time from failure to a clear status was reduced from hours of waiting to minutes.

The failure class is closed entirely: any failure is now visible to the user and reproducible in logs, instead of turning into a silently stuck progress bar.

What to do if the indicator is implemented and shows incorrect values

A discrepancy in the indicator almost always belongs to one of two classes of causes: an incorrect estimate of the amount of work or a loss of state transfer between the task worker and the interface.

They can be distinguished by the execution log: if the counter grows steadily but hits the upper bound ahead of time, the estimate is lying; if the values jump, roll back, or freeze, the state transfer is breaking.

Symptom Likely cause Minimal change
The bar reaches the end before the task has completed The volume is estimated by the number of records rather than by actual work Calculate the total volume at the start and refine it along the way
The value rolls back Several executors write to one counter Aggregate progress at the task level, not the executor level
The bar freezes midway Some stages do not publish events at all Cover the missing stages with publication
After a restart, the count starts from zero State lives only in process memory Store a progress snapshot in a shared store

Next, the scope of changes is decided. There is no need to rewrite the entire queue: a unified progress event contract is enough — one publication point and an explicit percentage recalculation rule.

Compatibility with the current queue is preserved, because the reporting layer changes, not the submission and ordering of tasks.

The diagnostic procedure is as follows: reconcile the log with the volume estimate, check aggregation by task, and only then touch the state storage schema. This way, the intervention remains a partial refactoring.

Technical doubts: reliability, maintenance, and retries

Some questions arise before the start: they are about system behavior under abnormal conditions, not about the appearance of the bar. Below are answers to the ones asked most often.

How to calculate progress if the total volume is unknown in advance?

The bar relies on processed records and confirmed batches. If the volume is refined along the way, we show the current stage and an estimate of the remaining time: an honestly refining estimate is more useful than a frozen number.

What happens when a worker fails?

The job state is stored in the task log, not in process memory. A failure is recorded as a stop at a specific batch, with the error and context recorded. A retry continues from the last confirmed batch, not from the beginning.

Several users are watching the task at the same time — does that interfere?

Each client is subscribed to the job channel and receives only status changes. The number of observers does not affect processing speed: watching with ten people is the same as watching alone.

Will the solution withstand peak load?

Subscribing to progress is cheaper than the processing itself: clients receive only state changes. Jobs are queued, the worker takes the next one after it becomes free — a spike does not turn into an outage.

What remains for maintenance after handover?

Metrics for stage durations, alerts about stuck jobs, and a failure log with context. New stages are added by separate workers and do not affect those already running.

Let's discuss the task and come back with an estimate and work plan

To come back with an estimate rather than assumptions, we need baseline data on how the current task queue and interface are structured.

The progress bar is essentially a contract between the server and the client: the task state must survive a page reload, a worker restart, and a retry after a failure.

Therefore, we start with an inventory of what already works, and only then propose a status schema and implementation order.

  • How the task currently enters the queue and where its state is stored: a database table, cache, or a separate queue service.
  • Whether the long-running process is split into steps and whether their total number can be calculated in advance — this determines whether the progress bar will be honest or remain a "spinner."
  • How the interface learns about changes: infrequent timer-based polling, an event stream, or a persistent communication channel.
  • What is considered normal when closing a tab, a worker crash, and a retry — which scenarios are acceptable and which must be excluded.
  • How many tasks run simultaneously and how deep the status history should be.
  • Interface requirements: where progress is shown, whether cancellation is needed, and what the user sees on error.
  • Environment constraints: access to staging environments, deployment regulations, and whether the data schema can be changed on a live system.

Based on these answers, we assemble a status schema — from queuing to completion, cancellation, and error — and break the implementation into stages with a clear order.

Describe the task to the extent that it exists: we will come back with an estimate, a status schema, and a work plan.

Our competencies:

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1393
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1368
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1139
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1152
  • Website development for SBH Partners
    Website development for SBH Partners
    1231
  • Website development for Red Pear
    Website development for Red Pear
    641