Skip to content

Commit 7f60a48

Browse files
committed
fix(cloudflare): detect legacy assets mode
Fixes #523
1 parent ed8db8d commit 7f60a48

File tree

7 files changed

+200
-46
lines changed

7 files changed

+200
-46
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
# Changelog
22

33

4+
## v6.2.1...main
5+
6+
[compare changes](https://github.com/nuxt-modules/og-image/compare/v6.2.1...main)
7+
8+
### 🏡 Chore
9+
10+
- Bump deps ([caf70605](https://github.com/nuxt-modules/og-image/commit/caf70605))
11+
12+
### ❤️ Contributors
13+
14+
- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw))
15+
416
## v6.2.0...main
517

618
[compare changes](https://github.com/nuxt-modules/og-image/compare/v6.2.0...main)

docs/content/0.getting-started/3.troubleshooting.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ This will show you your OG Image and give you all of the debug information.
1717

1818
You can enable the [debug](/docs/og-image/api/config#debug) option which will give you more granular output.
1919

20+
### Text Missing on Cloudflare Workers
21+
22+
If your OG images render the background and images but all text is invisible, the [Cloudflare](https://cloudflare.com) `ASSETS` binding is not configured. Without it, the worker cannot load font files at runtime.
23+
24+
See the [Cloudflare deployment guide](/docs/og-image/guides/cloudflare) for the fix.
25+
2026
### Long OG Image URLs
2127

2228
If your runtime OG image URLs are long (500+ characters), v6 expects this - it encodes all options directly in the URL path for stateless CDN caching. See the [Performance](/docs/og-image/guides/performance) guide for strategies to reduce URL size and optimise rendering.

docs/content/2.renderers/0.index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Each renderer can use different bindings depending on the environment:
6666
- Satori and Takumi use Wasm automatically
6767
- Browser available via [Cloudflare Browser Rendering](/docs/og-image/renderers/browser#cloudflare-browser-rendering) binding
6868
- Sharp unavailable
69+
- Requires the `ASSETS` binding for font loading at runtime. See [Cloudflare deployment](/docs/og-image/guides/cloudflare) for setup
6970

7071
### Overriding Compatibility
7172

docs/content/2.renderers/3.browser.md

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -86,51 +86,11 @@ Check the [compatibility](/docs/og-image/guides/compatibility) guide for more in
8686

8787
For Cloudflare Workers/Pages deployments, you can use [Cloudflare Browser Rendering](https://developers.cloudflare.com/browser-rendering/) for runtime screenshots.
8888

89-
### Installation
90-
91-
```bash
92-
pnpm add @cloudflare/puppeteer
93-
```
94-
95-
### Configuration
96-
97-
Configure the browser provider and binding name in your `nuxt.config.ts`:
98-
99-
```ts [nuxt.config.ts]
100-
export default defineNuxtConfig({
101-
ogImage: {
102-
browser: {
103-
provider: 'cloudflare',
104-
binding: 'BROWSER' // your wrangler binding name
105-
}
106-
}
107-
})
108-
```
109-
110-
Configure the browser binding in `wrangler.toml`:
111-
112-
```toml [wrangler.toml]
113-
name = "my-worker"
114-
compatibility_date = "2025-09-15"
115-
nodejs_compat = true
116-
117-
[browser]
118-
binding = "BROWSER"
119-
```
120-
121-
### How It Works
89+
See the [Cloudflare deployment guide](/docs/og-image/guides/cloudflare#browser-rendering) for setup instructions.
12290

12391
When using the [Cloudflare](https://cloudflare.com) provider:
12492
- **Development:** Uses local `chrome-launcher` (zero config)
12593
- **Prerender/Build:** Uses `playwright`
12694
- **Production Runtime:** Uses Cloudflare Browser Rendering
12795

12896
The module automatically handles session reuse and timeout handling for optimal performance within Cloudflare's rate limits.
129-
130-
### Limitations
131-
132-
- Free plan: 3 new browsers/minute
133-
- Paid plan: 30 new browsers/minute
134-
- 60-second idle timeout (handled automatically)
135-
136-
See [Cloudflare Browser Rendering limits](https://developers.cloudflare.com/browser-rendering/limits/) for details.
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---
2+
title: Cloudflare Deployment
3+
description: Deploy Nuxt OG Image to Cloudflare Workers or Pages.
4+
navigation:
5+
title: 'Cloudflare'
6+
---
7+
8+
Nuxt OG Image works on Cloudflare Workers and Pages with the [Takumi](/docs/og-image/renderers/takumi) (recommended) or [Satori](/docs/og-image/renderers/satori) renderer using Wasm bindings.
9+
10+
## Static Assets (ASSETS Binding)
11+
12+
OG image generation requires loading font files at runtime. On [Cloudflare](https://cloudflare.com), fonts are served as static assets through the [`ASSETS` binding](https://developers.cloudflare.com/workers/static-assets/binding/). Without this binding, the worker cannot access fonts and all text will be invisible.
13+
14+
### Cloudflare Pages
15+
16+
No extra configuration needed. The `wrangler pages deploy` command configures the `ASSETS` binding automatically.
17+
18+
### Cloudflare Workers (Module)
19+
20+
Enable `deployConfig` so Nitro generates a `wrangler.json` with the `ASSETS` binding:
21+
22+
```ts [nuxt.config.ts]
23+
export default defineNuxtConfig({
24+
nitro: {
25+
preset: 'cloudflare-module',
26+
cloudflare: {
27+
deployConfig: true,
28+
},
29+
},
30+
})
31+
```
32+
33+
Then build and deploy:
34+
35+
```bash
36+
nuxt build
37+
npx wrangler --cwd .output deploy
38+
```
39+
40+
::warning
41+
Do **not** deploy with `wrangler deploy --assets .output/public`. This legacy command does not configure the `ASSETS` binding. Use `npx wrangler --cwd .output deploy` which reads the generated `wrangler.json`.
42+
::
43+
44+
You can verify the binding exists in the deploy output:
45+
46+
```
47+
Your Worker has access to the following bindings:
48+
Binding Resource
49+
env.ASSETS Assets
50+
```
51+
52+
### Custom Wrangler Config
53+
54+
If you manage your own `wrangler.toml`, add the `[assets]` section:
55+
56+
```toml [wrangler.toml]
57+
[assets]
58+
binding = "ASSETS"
59+
directory = ".output/public"
60+
```
61+
62+
## Runtime Cache with KV
63+
64+
Rendered OG images are cached in memory by default, which resets on every deploy. For persistent caching, use [Cloudflare KV](https://developers.cloudflare.com/kv/).
65+
66+
### 1. Create a KV Namespace
67+
68+
```bash
69+
npx wrangler kv namespace create OG_IMAGE_CACHE
70+
```
71+
72+
### 2. Add the Binding
73+
74+
::code-group
75+
76+
```toml [wrangler.toml]
77+
[[kv_namespaces]]
78+
binding = "OG_IMAGE_CACHE"
79+
id = "<your-namespace-id>"
80+
```
81+
82+
```ts [nuxt.config.ts]
83+
export default defineNuxtConfig({
84+
nitro: {
85+
cloudflare: {
86+
wrangler: {
87+
kv_namespaces: [
88+
{ binding: 'OG_IMAGE_CACHE', id: '<your-namespace-id>' },
89+
],
90+
},
91+
},
92+
},
93+
})
94+
```
95+
96+
::
97+
98+
### 3. Configure Cache Storage
99+
100+
```ts [nuxt.config.ts]
101+
export default defineNuxtConfig({
102+
ogImage: {
103+
runtimeCacheStorage: {
104+
driver: 'cloudflare-kv-binding',
105+
binding: 'OG_IMAGE_CACHE',
106+
},
107+
},
108+
})
109+
```
110+
111+
See the [Cache guide](/docs/og-image/guides/cache) for more caching options.
112+
113+
## Browser Rendering
114+
115+
[Cloudflare Browser Rendering](https://developers.cloudflare.com/browser-rendering/) enables runtime screenshots for `.browser.vue` templates. This is not needed for Takumi or Satori templates.
116+
117+
### 1. Install the Dependency
118+
119+
```bash
120+
pnpm add @cloudflare/puppeteer
121+
```
122+
123+
### 2. Add the Binding
124+
125+
::code-group
126+
127+
```toml [wrangler.toml]
128+
[browser]
129+
binding = "BROWSER"
130+
```
131+
132+
```ts [nuxt.config.ts]
133+
export default defineNuxtConfig({
134+
ogImage: {
135+
browser: {
136+
provider: 'cloudflare',
137+
binding: 'BROWSER',
138+
},
139+
},
140+
})
141+
```
142+
143+
::
144+
145+
### Rate Limits
146+
147+
| Plan | New browsers per minute |
148+
|------|------------------------|
149+
| Free | 3 |
150+
| Paid | 30 |
151+
152+
Sessions have a 60 second idle timeout, handled automatically by the module. See [Cloudflare Browser Rendering limits](https://developers.cloudflare.com/browser-rendering/limits/) for details.
153+
154+
## Troubleshooting
155+
156+
### Text missing from OG images
157+
158+
If the background and images render but text is invisible, the `ASSETS` binding is missing. Verify your deployment follows the [setup above](#static-assets-assets-binding).
159+
160+
To diagnose, enable `ogImage.debug` and check the `.json` variant of your OG image URL. If `fonts` is an empty array, fonts failed to load.
161+
162+
### Fonts load locally but not in production
163+
164+
`wrangler dev` injects the `ASSETS` binding automatically, so fonts work in local preview. Production deployments need the binding configured explicitly via `deployConfig: true` or a `wrangler.toml` with `[assets]`.

docs/content/3.guides/3.cache.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ export default defineNuxtConfig({
8888
}
8989
}
9090
})
91-
````
91+
```
92+
93+
See the [Cloudflare guide](/docs/og-image/guides/cloudflare#runtime-cache-with-kv) for full KV setup instructions.
9294

9395
### Using Runtime Config
9496

src/runtime/server/og-image/bindings/font-assets/cloudflare.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ export async function resolve(event: H3Event, font: FontConfig) {
99
const fullPath = withBase(path, app.baseURL)
1010

1111
// Try ASSETS binding first (Cloudflare Pages / Workers with static assets)
12+
// This is the recommended approach and requires either:
13+
// - nitro.cloudflare.deployConfig: true (generates wrangler.json with ASSETS binding)
14+
// - A wrangler.toml/json with [assets] configured
1215
const assets = event.context.cloudflare?.env?.ASSETS || event.context.ASSETS
1316
if (assets && typeof assets.fetch === 'function') {
1417
const origin = event.context.cloudflare?.request?.url || `https://${event.headers.get('host') || 'localhost'}`
@@ -21,9 +24,6 @@ export async function resolve(event: H3Event, font: FontConfig) {
2124

2225
// Fallback: use event.fetch (Nitro localFetch) which routes through the h3 app
2326
// and can serve public assets from Nitro's built-in asset handler.
24-
// This is needed for Workers Static Assets (WSA) deployments where the ASSETS
25-
// binding is not injected into env (assets are served at the runtime level
26-
// before the worker's fetch runs).
2727
if (typeof event.fetch === 'function') {
2828
const origin = event.context.cloudflare?.request?.url || `https://${event.headers.get('host') || 'localhost'}`
2929
const url = new URL(fullPath, origin).href
@@ -33,5 +33,14 @@ export async function resolve(event: H3Event, font: FontConfig) {
3333
}
3434
}
3535

36-
throw new Error(`[Nuxt OG Image] Cannot resolve font "${font.family}" on Cloudflare Workers: no ASSETS binding or event.fetch available. Ensure static assets are configured.`)
36+
if (!assets && !event.context._ogImageWarnedMissingAssets) {
37+
event.context._ogImageWarnedMissingAssets = true
38+
console.warn(
39+
`[Nuxt OG Image] No ASSETS binding found on Cloudflare Workers. Font loading will fail. `
40+
+ `To fix this, add \`nitro: { cloudflare: { deployConfig: true } }\` to your nuxt.config `
41+
+ `and deploy with \`npx wrangler --cwd .output deploy\` instead of using the --assets flag.`,
42+
)
43+
}
44+
45+
throw new Error(`[Nuxt OG Image] Cannot resolve font "${font.family}" on Cloudflare Workers: no ASSETS binding available (path: ${fullPath}).`)
3746
}

0 commit comments

Comments
 (0)