lazy-file
The lazy-file package provides LazyFile and LazyBlob, which are drop-in replacements for the standard File and Blob classes that defer reading content until it is actually accessed. This makes them ideal for serving files from disk or object storage without loading entire files into memory upfront.
Installation
The lazy file package is included with Remix. No additional installation is needed.
Import
import { LazyFile, LazyBlob } from 'remix/lazy-file'API
LazyFile
A File-compatible object that defers reading its content. It implements the full File interface (name, type, size, lastModified, arrayBuffer(), text(), stream(), slice()).
class LazyFile extends LazyBlob implements File {
constructor(
read: () => ReadableStream<Uint8Array> | Promise<ReadableStream<Uint8Array>>,
name: string,
options: LazyFileOptions,
)
readonly name: string
readonly lastModified: number
}| Option | Type | Description |
|---|---|---|
type | string | The MIME type of the file. |
size | number | The byte length of the file. |
lastModified | number | Timestamp in milliseconds. Defaults to Date.now(). |
The read function is called only when the file's content is actually accessed via arrayBuffer(), text(), stream(), or slice().
let file = new LazyFile(
() => fetch('https://example.com/photo.jpg').then(r => r.body!),
'photo.jpg',
{ type: 'image/jpeg', size: 1048576 },
)
// No network request has been made yet
console.log(file.name) // 'photo.jpg'
console.log(file.size) // 1048576
// The read function is called now
let stream = file.stream()LazyBlob
A Blob-compatible object with deferred content reading.
class LazyBlob implements Blob {
constructor(
read: () => ReadableStream<Uint8Array> | Promise<ReadableStream<Uint8Array>>,
options: LazyBlobOptions,
)
readonly type: string
readonly size: number
arrayBuffer(): Promise<ArrayBuffer>
text(): Promise<string>
stream(): ReadableStream<Uint8Array>
slice(start?: number, end?: number, type?: string): Blob
}| Option | Type | Description |
|---|---|---|
type | string | The MIME type of the blob. |
size | number | The byte length of the blob. |
getByteLength()
Both LazyFile and LazyBlob expose their size through the standard size property without loading content into memory. This is useful for setting Content-Length headers or checking file sizes before streaming.
let file = new LazyFile(readFn, 'video.mp4', {
type: 'video/mp4',
size: 104857600, // 100 MB
})
// No content loaded -- size is known immediately
console.log(file.size) // 104857600Byte Range Support
slice() creates a new blob representing a sub-range of the content. Combined with lazy reading, this enables efficient byte-range serving.
let file = new LazyFile(readFn, 'video.mp4', {
type: 'video/mp4',
size: 104857600,
})
// Serve bytes 0-1023
let chunk = file.slice(0, 1024)
let stream = chunk.stream()Examples
Efficient File Serving
Serve a file from a URL without buffering it in memory:
import { LazyFile } from 'remix/lazy-file'
import { createFileResponse } from 'remix/response/file'
router.map(fileRoute, async ({ request, params }) => {
let metadata = await getFileMetadata(params.id)
let file = new LazyFile(
() => fetch(metadata.url).then(r => r.body!),
metadata.name,
{
type: metadata.mimeType,
size: metadata.size,
},
)
return createFileResponse(file, request)
})Lazy File from Storage
import { LazyFile } from 'remix/lazy-file'
let file = new LazyFile(
() => storage.getStream('avatars/user-123.jpg'),
'avatar.jpg',
{ type: 'image/jpeg', size: 50000 },
)
// Pass to a response -- content is streamed on demand
return new Response(file.stream(), {
headers: {
'Content-Type': file.type,
'Content-Length': String(file.size),
},
})Lazy Blob for API Responses
import { LazyBlob } from 'remix/lazy-file'
let blob = new LazyBlob(
() => generateReport(),
{ type: 'application/pdf', size: reportSize },
)
return new Response(blob.stream(), {
headers: { 'Content-Type': blob.type },
})Related
- fs --- Create LazyFile instances from disk with
openLazyFile. - response ---
createFileResponsefor serving files with ETag and range support. - file-storage --- File storage abstraction.