Most image upload systems assume one thing: the user has a stable internet connection.
That assumption breaks quickly on mobile devices.
A user might select 10 photos, lose connectivity halfway through the upload, close the app, and reopen it later. A traditional upload system may force them to start everything again.
An offline-friendly image upload system solves this by treating uploads as a queue rather than a single network request.
A reliable system typically looks like this:
User selects image
↓
Compress / resize image
↓
Save image locally
↓
Create upload queue record
↓
Check network connection
↓
Online?
/ \
Yes No
↓ ↓
Upload Wait in queue
↓ ↓
Success Network returns
↓ ↓
Delete Upload
local copy The important idea is simple:
Never depend on the network to preserve the user’s upload.
The local device should temporarily become the source of truth.
Instead of immediately sending the selected image to your server, first save it locally.
For example, an upload queue might contain:
{
id: "upload_123",
filePath: "/local/uploads/photo.jpg",
fileName: "photo.jpg",
status: "pending",
attempts: 0,
createdAt: Date.now()
} The database can be something like:
For larger applications, a proper database is preferable because you need reliable queue management.
Images can consume enormous amounts of bandwidth.
A 5 MB camera image may contain far more resolution than your application actually needs.
Before adding the image to the upload queue:
Original image
↓
Resize
↓
Compress
↓
Convert if necessary
↓
Save locally
↓
Queue for upload For example, you might resize an image to a maximum width of 1920px and use JPEG/WebP compression.
This provides three benefits:
Do not blindly compress every image to the same quality. Profile your application’s requirements first.
The queue is the heart of the system.
Each image should have a state.
PENDING
UPLOADING
COMPLETED
FAILED A more sophisticated system might use:
PENDING
UPLOADING
COMPLETED
RETRYING
FAILED
CANCELLED The queue allows your application to remember what still needs to be uploaded.
For example:
[
{
id: "1",
filePath: "/uploads/a.jpg",
status: "completed"
},
{
id: "2",
filePath: "/uploads/b.jpg",
status: "pending"
},
{
id: "3",
filePath: "/uploads/c.jpg",
status: "retrying"
}
] When connectivity returns, the application processes the pending items.
Your application should monitor connectivity rather than assuming that an HTTP request will succeed.
Conceptually:
if (isOnline()) {
processUploadQueue();
} When the device transitions from offline to online:
network.onChange((online) => {
if (online) {
processUploadQueue();
}
}); The exact implementation depends on your platform.
However, remember:
Being connected to Wi-Fi does not necessarily mean the internet is reachable.
Your upload logic should still handle failed requests.
Networks fail.
Servers timeout.
Users enter elevators.
Mobile connections disappear.
Your upload system should therefore retry temporary failures.
A simple strategy:
async function uploadWithRetry(item) {
let delay = 1000;
for (let attempt = 1; attempt <= 5; attempt++) {
try {
await upload(item);
return true;
} catch (error) {
await sleep(delay);
delay *= 2;
}
}
return false;
} This is called exponential backoff.
Instead of repeatedly hammering your server:
1s → 2s → 4s → 8s → 16s the client gradually increases the waiting period.
For large images or videos, retrying the entire file can be wasteful.
Imagine a 100 MB upload reaches 95% and the connection disappears.
Restarting from zero wastes bandwidth.
A better architecture supports resumable uploads:
File
↓
Chunk 1 ──→ Server
Chunk 2 ──→ Server
Chunk 3 ──→ Server
Chunk 4 ──→ Server
X connection lost
Reconnect
Chunk 4 ──→ Server
Chunk 5 ──→ Server The server keeps track of which chunks have already arrived.
This becomes particularly valuable when dealing with large media files.
Offline queues introduce another problem:
What happens if the same image is uploaded twice?
Suppose the server successfully receives an image, but the client never receives the success response because the connection disappears.
The client may assume the upload failed and retry.
You can solve this with an idempotency key.
Generate a unique identifier for each upload:
const uploadId = crypto.randomUUID(); Send it with the request:
POST /uploads
Idempotency-Key: upload_123 The server stores the key.
If the same request arrives again, the server recognizes it and avoids creating another copy.
Offline-friendly does not mean invisible.
The interface should tell users what is happening.
For example:
3 photos selected
✓ photo1.jpg — Uploaded
↑ photo2.jpg — Uploading...
⏳ photo3.jpg — Waiting for connection When offline:
You're offline.
Your 4 photos are saved and will upload
automatically when you're back online. This is much better than showing:
Upload failed and forcing the user to figure out what happened.
Once the server confirms a successful upload, you can remove the temporary local file.
if (uploadSuccessful) {
await removeLocalFile(item.filePath);
await markAsCompleted(item.id);
} But do this only after confirmation.
Never delete the local file immediately after starting the request.
Otherwise:
Start upload
↓
Delete local file
↓
Network fails
↓
Upload lost Instead:
Start upload
↓
Server confirms success
↓
Mark completed
↓
Delete temporary file A clean implementation can separate responsibilities:
ImagePicker
↓
ImageProcessor
↓
LocalStorage
↓
UploadQueue
↓
NetworkMonitor
↓
UploadManager
↓
API Each component has one job.
ImagePicker
Selects images.
ImageProcessor
Resizes and compresses them.
LocalStorage
Preserves files while offline.
UploadQueue
Tracks pending uploads.
NetworkMonitor
Detects connectivity changes.
UploadManager
Processes and retries uploads.
API
Receives and validates files.
This architecture is easier to test and maintain than putting everything inside one upload function.
The client is only half of the system.
Your backend should also handle:
For production systems, images are often better stored in object storage rather than directly on the application server.
For example:
Mobile App
↓
Upload API
↓
Object Storage
↓
CDN
↓
Users The database stores metadata such as:
{
id: "img_123",
userId: "user_456",
url: "...",
size: 245000,
mimeType: "image/jpeg",
uploadedAt: "..."
} An offline-friendly image upload system should follow one principle:
Save first. Upload second.
The network should determine when an image is uploaded—not whether the user’s image survives.
A robust system therefore:
Once you design uploads as a durable queue, rather than a single HTTP request, your application becomes significantly more reliable—especially on mobile networks where connectivity cannot be guaranteed.
Latest tech news and coding tips.
Imagine a user submits a form while their internet connection suddenly disappears. Normally, the request…
Cybercriminals don't always announce their presence. Many compromises are designed to remain unnoticed for weeks…
For years, jQuery was everywhere. If you were building websites in the 2010s, there was a…
Few things halt a developer’s flow faster than seeing the dreaded word: CONFLICT. A Git merge…
Spring Boot is one of the most popular frameworks for building REST APIs in Java.…
Why borrowing code is a skill—when you understand what you're copying. For years, "copy-paste developer"…