Blob

Add blob storage for images, videos, documents, and other files with `@vitehub/blob`.

ViteHub Blob adds blob storage for images, videos, documents, and other files with hosting-aware driver detection.

Getting started

Install the package

pnpm add https://pkg.pr.new/nuxt-hub/agent/@vitehub/blob@main

Choose an integration surface

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@vitehub/blob/nuxt'],
})

Set a driver

ViteHub auto-configures blob storage from your environment or hosting provider. Keep the same top-level blob key no matter which integration surface you use.

When no provider is detected, ViteHub stores files locally in .data/blob.

Use the filesystem driver during local development or smoke testing.

nitro.config.ts
import { defineNitroConfig } from 'nitro/config'

export default defineNitroConfig({
  modules: ['@vitehub/blob/nitro'],
  blob: {
    driver: 'fs',
    dir: '.data/my-blob-directory',
  },
})

The local filesystem driver is not suitable for production deployments.

:::

Install aws4fetch and set the S3 environment variables.

pnpm add aws4fetch
.env
S3_ACCESS_KEY_ID=your-access-key-id
S3_SECRET_ACCESS_KEY=your-secret-access-key
S3_BUCKET=your-bucket-name
S3_REGION=eu-central-1
S3_ENDPOINT= # optional

Install @vercel/blob, then either deploy on Vercel or set BLOB_READ_WRITE_TOKEN locally.

pnpm add @vercel/blob
.env
BLOB_READ_WRITE_TOKEN=your-token

Files stored in Vercel Blob are public. Use another driver if you need private object storage.

Set the driver explicitly and provide the bucket name. ViteHub generates the Wrangler R2 binding for you.

nitro.config.ts
import { defineNitroConfig } from 'nitro/config'

export default defineNitroConfig({
  modules: ['@vitehub/blob/nitro'],
  blob: {
    driver: 'cloudflare-r2',
    bucketName: 'uploads',
    jurisdiction: 'eu',
  },
})
When you use R2 outside Cloudflare Workers, use the S3 driver with the R2 S3 API instead.

::

Upload a file

server/api/files.post.ts
import { blob } from '@vitehub/blob'

export default defineEventHandler(async () => {
  return await blob.put('avatars/user-1.txt', 'hello blob', {
    contentType: 'text/plain',
  })
})
Learn how to validate uploads and handle multipart requests.

::

Automatic configuration

Registering the integration without a blob config object keeps the setup small while following the same provider order as NuxtHub:

  1. Explicit driver
  2. S3_* environment variables
  3. Vercel hosting or BLOB_READ_WRITE_TOKEN
  4. Cloudflare hosting
  5. Local filesystem

Driver options

You can always pin a driver explicitly.

nitro.config.ts
import { defineNitroConfig } from 'nitro/config'

export default defineNitroConfig({
  modules: ['@vitehub/blob/nitro'],
  blob: {
    driver: 'fs',
    dir: '.data/files',
  },
})

Docker and Kubernetes deployments

Build container images without blob credentials by deferring S3 environment resolution to runtime.

nitro.config.ts
import { defineNitroConfig } from 'nitro/config'

export default defineNitroConfig({
  modules: ['@vitehub/blob/nitro'],
  blob: {
    driver: 's3',
  },
})

Set the credentials when the container starts:

.env
S3_ACCESS_KEY_ID=your-access-key-id
S3_SECRET_ACCESS_KEY=your-secret-access-key
S3_BUCKET=your-bucket-name
S3_REGION=eu-central-1
S3_ENDPOINT= # optional
When the default blob config falls back to Local at build time, ViteHub still checks S3_* first and BLOB_READ_WRITE_TOKEN second at runtime before using the filesystem driver.

Nuxt Image integration

@nuxt/image can optimize images that you expose through a blob-backed route.

Install @nuxt/image

pnpm add @nuxt/image

Expose blob files on a route

Create a route that serves blobs through blob.serve():

server/routes/images/[...pathname].get.ts
import { createError, defineEventHandler, getRouterParam } from 'h3'
import { blob } from '@vitehub/blob'

export default defineEventHandler(async (event) => {
  const pathname = getRouterParam(event, 'pathname')
  if (!pathname)
    throw createError({ statusCode: 404, statusMessage: 'Not Found' })

  return await blob.serve(event, pathname)
})

Use a production image provider only in production

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@vitehub/blob/nuxt'],
  image: { provider: 'none' },
  $production: {
    image: {
      provider: 'cloudflare',
    },
  },
})
Cloudflare and Vercel image providers generate production-only URLs. In development, keep provider: 'none' so routes like /images/** still resolve directly through your Nitro server.

Serve blobs on a route

Control the content types you serve from user-generated blobs to avoid XSS issues.

Add a restrictive CSP header on the serving route when you only need raw file delivery:

server/routes/files/[...pathname].get.ts
import { createError, defineEventHandler, getRouterParam, setHeader } from 'h3'
import { blob } from '@vitehub/blob'

export default defineEventHandler(async (event) => {
  const pathname = getRouterParam(event, 'pathname')
  if (!pathname)
    throw createError({ statusCode: 404, statusMessage: 'Not Found' })

  setHeader(event, 'Content-Security-Policy', 'default-src \'none\';')
  return await blob.serve(event, pathname)
})