0
# Error Handling
1
2
Comprehensive error system with specific error types for different failure scenarios and detailed error messages.
3
4
## Overview
5
6
The Vercel Blob library provides a comprehensive error handling system with specific error classes for different types of failures. All errors extend from the base `BlobError` class and include detailed error messages to help developers understand and handle various failure scenarios.
7
8
## Error Hierarchy
9
10
All blob-related errors inherit from the base `BlobError` class, which extends the standard JavaScript `Error`.
11
12
```typescript { .api }
13
class BlobError extends Error {
14
constructor(message: string);
15
}
16
```
17
18
## Error Types
19
20
### Access and Authentication Errors
21
22
#### BlobAccessError
23
24
Thrown when the request lacks proper authentication or authorization.
25
26
```typescript { .api }
27
/**
28
* Thrown when access is denied due to authentication or authorization issues
29
*/
30
class BlobAccessError extends BlobError {
31
constructor();
32
}
33
```
34
35
**Common Causes:**
36
- Invalid or missing `BLOB_READ_WRITE_TOKEN`
37
- Expired authentication token
38
- Insufficient permissions for the requested operation
39
- Invalid client token
40
41
**Example:**
42
43
```typescript
44
import { put, BlobAccessError } from '@vercel/blob';
45
46
try {
47
await put('test.txt', 'content', {
48
access: 'public',
49
token: 'invalid-token',
50
});
51
} catch (error) {
52
if (error instanceof BlobAccessError) {
53
console.error('Access denied:', error.message);
54
// Handle authentication error
55
}
56
}
57
```
58
59
### Resource Errors
60
61
#### BlobNotFoundError
62
63
Thrown when attempting to access a blob that doesn't exist.
64
65
```typescript { .api }
66
/**
67
* Thrown when a requested blob cannot be found
68
*/
69
class BlobNotFoundError extends BlobError {
70
constructor();
71
}
72
```
73
74
**Common Operations:**
75
- `head()` on non-existent blob
76
- `del()` on non-existent blob
77
- `copy()` from non-existent source
78
79
**Example:**
80
81
```typescript
82
import { head, BlobNotFoundError } from '@vercel/blob';
83
84
try {
85
const metadata = await head('non-existent-file.txt');
86
} catch (error) {
87
if (error instanceof BlobNotFoundError) {
88
console.log('File does not exist');
89
// Handle missing file
90
}
91
}
92
```
93
94
#### BlobStoreNotFoundError
95
96
Thrown when the blob store itself cannot be found.
97
98
```typescript { .api }
99
/**
100
* Thrown when the blob store cannot be found
101
*/
102
class BlobStoreNotFoundError extends BlobError {
103
constructor();
104
}
105
```
106
107
**Common Causes:**
108
- Incorrect store configuration
109
- Store has been deleted
110
- Invalid store identifier in token
111
112
#### BlobStoreSuspendedError
113
114
Thrown when operations are attempted on a suspended blob store.
115
116
```typescript { .api }
117
/**
118
* Thrown when attempting operations on a suspended blob store
119
*/
120
class BlobStoreSuspendedError extends BlobError {
121
constructor();
122
}
123
```
124
125
**Common Causes:**
126
- Account billing issues
127
- Terms of service violations
128
- Administrative suspension
129
130
### Content and Validation Errors
131
132
#### BlobContentTypeNotAllowedError
133
134
Thrown when uploading content with a disallowed MIME type.
135
136
```typescript { .api }
137
/**
138
* Thrown when the content type is not allowed for upload
139
*/
140
class BlobContentTypeNotAllowedError extends BlobError {
141
constructor(message: string);
142
}
143
```
144
145
**Common Causes:**
146
- Uploading executable files
147
- Restricted file types based on store configuration
148
- Invalid or missing content type
149
150
**Example:**
151
152
```typescript
153
import { put, BlobContentTypeNotAllowedError } from '@vercel/blob';
154
155
try {
156
await put('script.exe', executableData, {
157
access: 'public',
158
contentType: 'application/x-executable',
159
});
160
} catch (error) {
161
if (error instanceof BlobContentTypeNotAllowedError) {
162
console.error('File type not allowed:', error.message);
163
// Handle content type restriction
164
}
165
}
166
```
167
168
#### BlobPathnameMismatchError
169
170
Thrown when there's a mismatch between expected and actual pathname.
171
172
```typescript { .api }
173
/**
174
* Thrown when pathname doesn't match expected format or constraints
175
*/
176
class BlobPathnameMismatchError extends BlobError {
177
constructor(message: string);
178
}
179
```
180
181
**Common Causes:**
182
- Pathname too long (exceeds `MAXIMUM_PATHNAME_LENGTH`)
183
- Invalid characters in pathname
184
- Pathname format violations
185
186
#### BlobFileTooLargeError
187
188
Thrown when uploading files that exceed size limits.
189
190
```typescript { .api }
191
/**
192
* Thrown when file size exceeds the allowed limit
193
*/
194
class BlobFileTooLargeError extends BlobError {
195
constructor(message: string);
196
}
197
```
198
199
**Common Causes:**
200
- Single file exceeds server upload limit (4.5MB on Vercel)
201
- Multipart part smaller than minimum size (5MB, except last part)
202
- Total file size exceeds maximum (5TB)
203
204
### Service and Network Errors
205
206
#### BlobServiceNotAvailable
207
208
Thrown when the blob service is temporarily unavailable.
209
210
```typescript { .api }
211
/**
212
* Thrown when the blob service is temporarily unavailable
213
*/
214
class BlobServiceNotAvailable extends BlobError {
215
constructor();
216
}
217
```
218
219
**Common Causes:**
220
- Service maintenance
221
- Temporary outages
222
- Network connectivity issues
223
224
**Handling:**
225
226
```typescript
227
import { put, BlobServiceNotAvailable } from '@vercel/blob';
228
229
async function uploadWithRetry(pathname: string, body: any, maxRetries = 3) {
230
for (let attempt = 1; attempt <= maxRetries; attempt++) {
231
try {
232
return await put(pathname, body, { access: 'public' });
233
} catch (error) {
234
if (error instanceof BlobServiceNotAvailable && attempt < maxRetries) {
235
console.log(`Service unavailable, retrying... (${attempt}/${maxRetries})`);
236
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
237
continue;
238
}
239
throw error;
240
}
241
}
242
}
243
```
244
245
#### BlobServiceRateLimited
246
247
Thrown when requests exceed the rate limit.
248
249
```typescript { .api }
250
/**
251
* Thrown when requests exceed the allowed rate limit
252
*/
253
class BlobServiceRateLimited extends BlobError {
254
public readonly retryAfter: number;
255
constructor(seconds?: number);
256
}
257
```
258
259
**Handling:**
260
261
```typescript
262
import { put, BlobServiceRateLimited } from '@vercel/blob';
263
264
async function uploadWithRateLimit(pathname: string, body: any) {
265
try {
266
return await put(pathname, body, { access: 'public' });
267
} catch (error) {
268
if (error instanceof BlobServiceRateLimited) {
269
console.log('Rate limited, waiting before retry...');
270
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
271
return put(pathname, body, { access: 'public' });
272
}
273
throw error;
274
}
275
}
276
```
277
278
### Request and Token Errors
279
280
#### BlobRequestAbortedError
281
282
Thrown when a request is aborted using an AbortSignal.
283
284
```typescript { .api }
285
/**
286
* Thrown when a request is aborted via AbortSignal
287
*/
288
class BlobRequestAbortedError extends BlobError {
289
constructor();
290
}
291
```
292
293
**Example:**
294
295
```typescript
296
import { put, BlobRequestAbortedError } from '@vercel/blob';
297
298
const controller = new AbortController();
299
300
// Cancel upload after 5 seconds
301
setTimeout(() => controller.abort(), 5000);
302
303
try {
304
await put('large-file.bin', largeData, {
305
access: 'public',
306
abortSignal: controller.signal,
307
});
308
} catch (error) {
309
if (error instanceof BlobRequestAbortedError) {
310
console.log('Upload was cancelled');
311
}
312
}
313
```
314
315
#### BlobClientTokenExpiredError
316
317
Thrown when a client token has expired.
318
319
```typescript { .api }
320
/**
321
* Thrown when a client token has expired
322
*/
323
class BlobClientTokenExpiredError extends BlobError {
324
constructor();
325
}
326
```
327
328
**Handling:**
329
330
```typescript
331
import { upload, BlobClientTokenExpiredError } from '@vercel/blob/client';
332
333
async function uploadWithTokenRefresh(pathname: string, body: any) {
334
try {
335
return await upload(pathname, body, {
336
access: 'public',
337
handleUploadUrl: '/api/upload',
338
});
339
} catch (error) {
340
if (error instanceof BlobClientTokenExpiredError) {
341
console.log('Token expired, will request new token automatically');
342
// The upload function will automatically retry with a new token
343
return upload(pathname, body, {
344
access: 'public',
345
handleUploadUrl: '/api/upload',
346
});
347
}
348
throw error;
349
}
350
}
351
```
352
353
### Generic Errors
354
355
#### BlobUnknownError
356
357
Thrown for unexpected or unhandled error conditions.
358
359
```typescript { .api }
360
/**
361
* Thrown for unknown or unexpected error conditions
362
*/
363
class BlobUnknownError extends BlobError {
364
constructor();
365
}
366
```
367
368
## Error Handling Patterns
369
370
### Basic Error Handling
371
372
```typescript
373
import {
374
put,
375
BlobError,
376
BlobAccessError,
377
BlobNotFoundError,
378
BlobFileTooLargeError
379
} from '@vercel/blob';
380
381
async function handleBlobOperation() {
382
try {
383
const result = await put('test.txt', 'content', {
384
access: 'public',
385
});
386
return result;
387
388
} catch (error) {
389
if (error instanceof BlobAccessError) {
390
console.error('Authentication failed:', error.message);
391
// Redirect to login or refresh token
392
393
} else if (error instanceof BlobFileTooLargeError) {
394
console.error('File too large:', error.message);
395
// Suggest using multipart upload
396
397
} else if (error instanceof BlobError) {
398
console.error('Blob operation failed:', error.message);
399
// Generic blob error handling
400
401
} else {
402
console.error('Unexpected error:', error);
403
// Handle non-blob errors
404
}
405
406
throw error; // Re-throw if needed
407
}
408
}
409
```
410
411
### Comprehensive Error Handler
412
413
```typescript
414
import {
415
BlobError,
416
BlobAccessError,
417
BlobNotFoundError,
418
BlobStoreNotFoundError,
419
BlobStoreSuspendedError,
420
BlobContentTypeNotAllowedError,
421
BlobPathnameMismatchError,
422
BlobFileTooLargeError,
423
BlobServiceNotAvailable,
424
BlobServiceRateLimited,
425
BlobRequestAbortedError,
426
BlobClientTokenExpiredError,
427
BlobUnknownError
428
} from '@vercel/blob';
429
430
interface ErrorInfo {
431
type: string;
432
message: string;
433
isRetryable: boolean;
434
suggestedAction: string;
435
}
436
437
function analyzeBlobError(error: unknown): ErrorInfo {
438
if (!(error instanceof BlobError)) {
439
return {
440
type: 'UnknownError',
441
message: String(error),
442
isRetryable: false,
443
suggestedAction: 'Check the error details and try again',
444
};
445
}
446
447
if (error instanceof BlobAccessError) {
448
return {
449
type: 'AccessError',
450
message: error.message,
451
isRetryable: false,
452
suggestedAction: 'Check authentication tokens and permissions',
453
};
454
}
455
456
if (error instanceof BlobNotFoundError) {
457
return {
458
type: 'NotFoundError',
459
message: error.message,
460
isRetryable: false,
461
suggestedAction: 'Verify the blob pathname or URL is correct',
462
};
463
}
464
465
if (error instanceof BlobStoreNotFoundError) {
466
return {
467
type: 'StoreNotFoundError',
468
message: error.message,
469
isRetryable: false,
470
suggestedAction: 'Check blob store configuration',
471
};
472
}
473
474
if (error instanceof BlobStoreSuspendedError) {
475
return {
476
type: 'StoreSuspendedError',
477
message: error.message,
478
isRetryable: false,
479
suggestedAction: 'Contact support to resolve account issues',
480
};
481
}
482
483
if (error instanceof BlobContentTypeNotAllowedError) {
484
return {
485
type: 'ContentTypeError',
486
message: error.message,
487
isRetryable: false,
488
suggestedAction: 'Use an allowed content type or modify store settings',
489
};
490
}
491
492
if (error instanceof BlobPathnameMismatchError) {
493
return {
494
type: 'PathnameError',
495
message: error.message,
496
isRetryable: false,
497
suggestedAction: 'Use a valid pathname format',
498
};
499
}
500
501
if (error instanceof BlobFileTooLargeError) {
502
return {
503
type: 'FileTooLargeError',
504
message: error.message,
505
isRetryable: false,
506
suggestedAction: 'Use multipart upload for large files',
507
};
508
}
509
510
if (error instanceof BlobServiceNotAvailable) {
511
return {
512
type: 'ServiceUnavailableError',
513
message: error.message,
514
isRetryable: true,
515
suggestedAction: 'Retry the operation after a delay',
516
};
517
}
518
519
if (error instanceof BlobServiceRateLimited) {
520
return {
521
type: 'RateLimitedError',
522
message: error.message,
523
isRetryable: true,
524
suggestedAction: 'Wait before retrying to respect rate limits',
525
};
526
}
527
528
if (error instanceof BlobRequestAbortedError) {
529
return {
530
type: 'RequestAbortedError',
531
message: error.message,
532
isRetryable: false,
533
suggestedAction: 'Restart the operation if needed',
534
};
535
}
536
537
if (error instanceof BlobClientTokenExpiredError) {
538
return {
539
type: 'TokenExpiredError',
540
message: error.message,
541
isRetryable: true,
542
suggestedAction: 'The operation will automatically retry with a new token',
543
};
544
}
545
546
if (error instanceof BlobUnknownError) {
547
return {
548
type: 'UnknownBlobError',
549
message: error.message,
550
isRetryable: false,
551
suggestedAction: 'Check the error details and contact support if needed',
552
};
553
}
554
555
return {
556
type: 'GenericBlobError',
557
message: error.message,
558
isRetryable: false,
559
suggestedAction: 'Review the error and try again',
560
};
561
}
562
563
// Usage example
564
async function robustBlobOperation() {
565
try {
566
return await put('test.txt', 'content', { access: 'public' });
567
} catch (error) {
568
const errorInfo = analyzeBlobError(error);
569
570
console.error(`${errorInfo.type}: ${errorInfo.message}`);
571
console.log(`Suggested action: ${errorInfo.suggestedAction}`);
572
573
if (errorInfo.isRetryable) {
574
console.log('This error is retryable');
575
// Implement retry logic
576
}
577
578
throw error;
579
}
580
}
581
```
582
583
### Retry with Exponential Backoff
584
585
```typescript
586
import {
587
BlobError,
588
BlobServiceNotAvailable,
589
BlobServiceRateLimited
590
} from '@vercel/blob';
591
592
async function withRetry<T>(
593
operation: () => Promise<T>,
594
maxRetries = 3,
595
baseDelay = 1000
596
): Promise<T> {
597
for (let attempt = 1; attempt <= maxRetries; attempt++) {
598
try {
599
return await operation();
600
} catch (error) {
601
const isRetryable =
602
error instanceof BlobServiceNotAvailable ||
603
error instanceof BlobServiceRateLimited;
604
605
if (!isRetryable || attempt === maxRetries) {
606
throw error;
607
}
608
609
const delay = baseDelay * Math.pow(2, attempt - 1);
610
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
611
await new Promise(resolve => setTimeout(resolve, delay));
612
}
613
}
614
615
throw new Error('Max retries exceeded');
616
}
617
618
// Usage
619
const result = await withRetry(async () => {
620
return put('test.txt', 'content', { access: 'public' });
621
});
622
```
623
624
### Error Boundaries for React
625
626
```typescript
627
import React from 'react';
628
import { BlobError } from '@vercel/blob';
629
630
interface Props {
631
children: React.ReactNode;
632
}
633
634
interface State {
635
hasError: boolean;
636
error?: Error;
637
}
638
639
class BlobErrorBoundary extends React.Component<Props, State> {
640
constructor(props: Props) {
641
super(props);
642
this.state = { hasError: false };
643
}
644
645
static getDerivedStateFromError(error: Error): State {
646
return { hasError: true, error };
647
}
648
649
componentDidCatch(error: Error) {
650
if (error instanceof BlobError) {
651
console.error('Blob operation failed:', error.message);
652
// Log to error reporting service
653
}
654
}
655
656
render() {
657
if (this.state.hasError) {
658
return (
659
<div className="error-boundary">
660
<h2>Upload Failed</h2>
661
<p>
662
{this.state.error instanceof BlobError
663
? 'There was an issue with the file upload. Please try again.'
664
: 'An unexpected error occurred.'}
665
</p>
666
<button onClick={() => this.setState({ hasError: false })}>
667
Try Again
668
</button>
669
</div>
670
);
671
}
672
673
return this.props.children;
674
}
675
}
676
```
677
678
## Constants
679
680
### Maximum Limits
681
682
```typescript { .api }
683
const MAXIMUM_PATHNAME_LENGTH = 950; // Maximum allowed pathname length
684
```
685
686
## Best Practices
687
688
### Error Logging
689
690
```typescript
691
import { BlobError } from '@vercel/blob';
692
693
function logBlobError(error: unknown, operation: string, context?: any) {
694
if (error instanceof BlobError) {
695
console.error('Blob Error:', {
696
operation,
697
type: error.constructor.name,
698
message: error.message,
699
context,
700
timestamp: new Date().toISOString(),
701
});
702
} else {
703
console.error('Non-Blob Error:', {
704
operation,
705
error: String(error),
706
context,
707
timestamp: new Date().toISOString(),
708
});
709
}
710
}
711
```
712
713
### Graceful Degradation
714
715
```typescript
716
import { put, BlobError } from '@vercel/blob';
717
718
async function uploadWithFallback(pathname: string, content: any) {
719
try {
720
return await put(pathname, content, { access: 'public' });
721
} catch (error) {
722
if (error instanceof BlobError) {
723
console.warn('Blob upload failed, falling back to local storage');
724
// Implement fallback logic
725
return { url: `local://fallback/${pathname}` };
726
}
727
throw error;
728
}
729
}
730
```