Skip to content

Commit 756e7be

Browse files
travisbreaksTadaoclaude
authored
fix(cloudflare): exclude queue consumers from prerender worker (#16225)
* fix(cloudflare): exclude queue consumers from prerender worker config The prerender worker's config callback was spreading the entire entryWorkerConfig, including queues.consumers. When Miniflare sees two workers both registered as consumers of the same queue, it rejects with ERR_MULTIPLE_CONSUMERS. The prerender worker only renders static HTML and has no need for queue consumer registrations. This fix destructures queues from the entry worker config and only preserves queue producers (bindings) in the prerender worker config. Closes #16199 Co-Authored-By: Tadao <tadao@travisfixes.com> * chore: update pnpm lockfile Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Tadao <tadao@travisfixes.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2c9bf5e commit 756e7be

9 files changed

Lines changed: 106 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/cloudflare': patch
3+
---
4+
5+
Fixes `ERR_MULTIPLE_CONSUMERS` error when using Cloudflare Queues with prerendered pages. The prerender worker config callback now excludes `queues.consumers` from the entry worker config, since the prerender worker only renders static HTML and should not register as a queue consumer. Queue producers (bindings) are preserved.

packages/integrations/cloudflare/src/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,15 @@ export default function createIntegration({
181181
experimental: {
182182
prerenderWorker: {
183183
config(_, { entryWorkerConfig }) {
184+
const { queues, ...restWorkerConfig } = entryWorkerConfig;
184185
return {
185-
...entryWorkerConfig,
186+
...restWorkerConfig,
186187
name: 'prerender',
188+
...(queues?.producers?.length && {
189+
queues: { producers: queues.producers },
190+
}),
187191
...(needsImagesBinding &&
188-
!entryWorkerConfig.images && {
192+
!restWorkerConfig.images && {
189193
images: { binding: imagesBindingName },
190194
}),
191195
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import cloudflare from '@astrojs/cloudflare';
2+
import { defineConfig } from 'astro/config';
3+
4+
export default defineConfig({
5+
adapter: cloudflare(),
6+
output: 'server',
7+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "@test/astro-cloudflare-prerender-queue-consumers",
3+
"version": "0.0.0",
4+
"private": true,
5+
"dependencies": {
6+
"@astrojs/cloudflare": "workspace:*",
7+
"astro": "workspace:*"
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { APIRoute } from 'astro';
2+
3+
export const prerender = false;
4+
5+
export const GET: APIRoute = async () => {
6+
return new Response(JSON.stringify({ ok: true }), {
7+
headers: { 'Content-Type': 'application/json' },
8+
});
9+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
// This page is prerendered by default (output: 'server' with no opt-out)
3+
// Actually, in output: 'server' mode, pages are server-rendered by default.
4+
// We explicitly mark this as prerendered.
5+
export const prerender = true;
6+
---
7+
<html>
8+
<head><title>Prerendered</title></head>
9+
<body><h1>Prerendered Page</h1></body>
10+
</html>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "prerender-queue-consumers",
3+
"main": "@astrojs/cloudflare/entrypoints/server",
4+
"compatibility_date": "2026-01-28",
5+
"queues": {
6+
"consumers": [
7+
{
8+
"queue": "my-queue"
9+
}
10+
],
11+
"producers": [
12+
{
13+
"binding": "MY_QUEUE",
14+
"queue": "my-queue"
15+
}
16+
]
17+
}
18+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import * as assert from 'node:assert/strict';
2+
import { after, before, describe, it } from 'node:test';
3+
import { loadFixture } from './_test-utils.js';
4+
5+
describe('Prerender with queue consumers', () => {
6+
let fixture;
7+
let previewServer;
8+
9+
before(async () => {
10+
fixture = await loadFixture({
11+
root: './fixtures/prerender-queue-consumers/',
12+
});
13+
await fixture.build();
14+
previewServer = await fixture.preview();
15+
});
16+
17+
after(async () => {
18+
previewServer.stop();
19+
});
20+
21+
it('builds and previews without ERR_MULTIPLE_CONSUMERS', async () => {
22+
// The prerendered page should be accessible
23+
const res = await fixture.fetch('/');
24+
const html = await res.text();
25+
assert.ok(html.includes('Prerendered Page'));
26+
});
27+
28+
it('serves the SSR endpoint', async () => {
29+
const res = await fixture.fetch('/api');
30+
const json = await res.json();
31+
assert.deepEqual(json, { ok: true });
32+
});
33+
});

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)