Storage

Provider-agnostic file upload, preview, and delete. Bytes go directly to a private bucket via short-lived presigned URLs. Cloudflare R2 is the first provider.

Tailar is a production-ready Next.js foundation. The CLI setup wizard composes templates, modules, and providers into code you own, including auth, billing, email, and database.

Last updated

Installation

1

Install the Package

Add Storage to your project using the Tailar CLI:

pnpm dlx tailar@latest add storage
2

Set Environment Variables

Create a `.env` file in the root of your project and add the following environment variables:

.env
1
# Storage provider. Currently r2.
2
STORAGE_PROVIDER=r2
3
 
4
# redirect: 302 to a short-lived presigned GET
5
# stream: proxy bytes through the Next.js route
6
STORAGE_DOWNLOAD_MODE=redirect
7
 
8
# Presigned PUT lifetime in seconds
9
STORAGE_UPLOAD_URL_EXPIRES_IN=300
10
 
11
# Presigned GET lifetime in seconds
12
STORAGE_DOWNLOAD_URL_EXPIRES_IN=60
13
 
14
# Cloudflare R2. Keep the bucket private (no public access, no r2.dev domain).
15
R2_ACCOUNT_ID=
16
R2_ACCESS_KEY_ID=
17
R2_SECRET_ACCESS_KEY=
18
R2_BUCKET=
19
 
20
# eu uses <ACCOUNT_ID>.eu.r2.cloudflarestorage.com. Use auto only for non-EU buckets.
21
R2_JURISDICTION=eu

Preview

Explore representative UI flows and snippets generated for this module (same previews as on the home page modules section).

Drop files here or click to browse

Images and documents up to 50 MB

  • report.pdf

    1.2 MB · Uploading

    x
  • avatar.png

    240 KB

File Tree

The following files are installed when you add Storage:

File Structure
1
├─ components/
2
│ └─ storage/
3
│ ├─ file-upload.tsx
4
│ └─ file-browser.tsx
5
├─ lib/
6
│ └─ storage/
7
│ └─ index.ts
8
└─ app/
9
└─ (protected)/
10
└─ (app)/
11
└─ files/
12
└─ page.tsx

Usage

Once you've installed Storage, you can start using the following actions:

createUpload

Authorize an upload and return a short-lived presigned PUT URL. The browser uploads bytes directly to the bucket, then calls finalizeUpload.

lib/storage/actions/upload.ts
1
import { createUpload, finalizeUpload } from '@/lib/storage'
2
 
3
const created = await createUpload({
4
scope: 'files',
5
fileName: file.name,
6
contentType: file.type,
7
size: file.size,
8
})
9
if (created.state === 'error') throw new Error(created.message)
10
 
11
await fetch(created.uploadUrl, {
12
method: 'PUT',
13
headers: created.headers,
14
body: file,
15
})
16
await finalizeUpload(created.file.id)

listFiles

List the signed-in user's READY files, optionally filtered by scope.

lib/storage/actions/files.ts
1
import { listFiles } from '@/lib/storage'
2
 
3
const result = await listFiles('files')
4
if (result.state === 'success') {
5
console.log(result.files)
6
}

deleteFile

Soft-delete a file the current user owns and remove the object from storage.

lib/storage/actions/files.ts
1
import { deleteFile } from '@/lib/storage'
2
 
3
const result = await deleteFile(fileId)
4
if (result.state === 'error') {
5
console.error(result.message)
6
}

FileUpload

Dropzone that uploads files for a scope, shows progress, and calls onReady when each file is stored.

components/storage/file-upload.tsx
1
import { FileUpload } from '@/components/storage'
2
 
3
<FileUpload scope="files" onReady={(files) => console.log(files)} />

References

For more information about Storage, check out these resources:

Cloudflare R2
Private object storage with the S3 API. Presigned URLs keep the bucket closed to the public.