ViteHub

Cloudflare Queue

Configure @vitehub/queue to publish through Cloudflare Queues and process Worker queue batches.

Use the Cloudflare provider when queue producers and consumers should run through Cloudflare Workers and Cloudflare Queues.

Cloudflare needs runtime queue bindings. ViteHub derives those bindings from discovered queue names and generates the Worker or Nitro queue wiring.

Configure Queue

Register the Vite plugin and set queue.provider to cloudflare:

vite.config.ts
import { defineConfig } from 'vite'
import { hubQueue } from '@vitehub/queue/vite'

export default defineConfig({
  plugins: [hubQueue()],
  queue: {
    provider: 'cloudflare',
  },
})

Understand generated names

Queue derives Cloudflare queue names and producer binding names from the discovered queue name.

For welcome-email:

ValueResult
Discovered queue namewelcome-email
Cloudflare queue namequeue--77656c636f6d652d656d61696c
Default producer bindingQUEUE_77656C636F6D652D656D61696C

Use these helpers when another config file needs the exact values:

import {
  getCloudflareQueueBindingName,
  getCloudflareQueueName,
} from '@vitehub/queue'

console.log(getCloudflareQueueName('welcome-email'))
console.log(getCloudflareQueueBindingName('welcome-email'))

Use an explicit binding name

Use queue.binding when you already provide one binding manually:

vite.config.ts
export default defineConfig({
  plugins: [hubQueue()],
  queue: {
    provider: 'cloudflare',
    binding: 'WELCOME_EMAIL_QUEUE',
  },
})

runQueue() resolves the binding from the current Cloudflare request environment.

Process batches

Cloudflare delivers batches of messages. Queue turns each message into a QueueJob and calls the discovered handler.

server/queues/welcome-email.ts
import { defineQueue } from '@vitehub/queue'

export default defineQueue<{ email: string }>(async (job) => {
  console.log(job.id, job.payload.email)
}, {
  concurrency: 4,
})

Use onError to control message retry behavior when a handler throws:

export default defineQueue(handler, {
  onError(error, message) {
    if (message.attempts >= 3) {
      return 'ack'
    }

    return { retry: { delaySeconds: 60 } }
  },
})

By default, failed messages are retried.

Generated output

Vite builds a Cloudflare Worker entry and a wrangler.json under dist/<app-name>.

When static Vite output contains index.html, the generated config also copies assets and runs the Worker first for /api/*.

Verify the provider

Call a route that enqueues a known queue:

curl -X POST http://localhost:3000/api/welcome \
  -H 'content-type: application/json' \
  -d '{"email":"ava@example.com","template":"vip"}'

Successful publishing returns a queued result:

{
  "ok": true,
  "result": {
    "status": "queued",
    "messageId": "queue_7f1b6f8e-7b5c-4c5e-b3a1-8d6a4b3d4c2a"
  }
}

Common failures

SymptomCauseFix
Cloudflare queue direct clients require a bindingA direct Cloudflare client was created without a binding object.Use runtime runQueue() in a Cloudflare request or pass a real binding to createQueueClient().
Cloudflare queue binding names require request-scoped runtime resolutionA binding name was used outside a request/runtime context.Run inside Cloudflare, or pass a direct binding object for tests.
Invalid Cloudflare queue bindingThe resolved binding does not expose send() and sendBatch().Check the binding name and Cloudflare queue configuration.
Cloudflare queue does not support enqueue optionsThe send envelope includes Vercel-only fields.Remove idempotencyKey and retentionSeconds.
Copyright © 2026