Skip to content

Commit c64fd33

Browse files
harlan-zwclaude
andauthored
feat!: nuxt/fonts integration (#432)
* feat: add provider dependency onboarding with Nuxt install/upgrade hooks - Move provider deps (satori, resvg, takumi, playwright, sharp, css-inline) to optional peerDependencies - Add onInstall hook with interactive provider/binding selection - Add onUpgrade hook for deprecated config detection - Validate renderer dependencies in setup, block if missing - Skip onboarding with NUXT_OG_IMAGE_SKIP_ONBOARDING=1 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: add cloudflare-takumi fixture for edge runtime e2e testing Tests takumi renderer with WASM bindings on cloudflare-pages preset Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: cloudflare workers compatibility and takumi WASM bindings - Fix takumi WASM import path and init API for @takumi-rs/wasm - Use virtual modules for edge runtime package stubs to avoid CJS resolution issues - Add canvas mock for linkedom on edge runtimes - Add wrangler.toml for cloudflare-takumi test fixture - Simplify cloudflare-takumi fixture config (auto-detects cloudflare-module preset) - Add .wrangler to .gitignore Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: move cloudflare-takumi e2e test to e2e-not-nuxt - Add comprehensive cloudflare-takumi test with prerendered and wrangler runtime tests - Move from test/e2e to test/e2e-not-nuxt (doesn't use @nuxt/test-utils) - Add snapshots for prerendered and runtime-generated images Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use solid color instead of gradient in cloudflare-takumi fixture Gradients don't work with takumi renderer, use solid bg-purple-600. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: add cloudflare-satori e2e test fixture Tests satori renderer with WASM bindings on cloudflare workers. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * doc: sync * chore: sync * chore: sync * feat: nuxt/fonts integration WIP * chore: sync * chore: sync * fix: offload fonts to public dir for edge runtimes Fixes #155 * chore: progress commit * chore: progress commit * chore: progress commit * chore: progress commit * chore: progress commit * chore: progress commit * chore: progress commit * chore: typecheck * chore: typecheck * fix: wrong fonts bindings for runtime * chore: missing fixtures * fix: respect base URL * chore: sync * chore: sync * chore: sync --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0e04643 commit c64fd33

78 files changed

Lines changed: 1188 additions & 907 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export default defineBuildConfig({
2626
'unstorage/drivers/fs',
2727
'consola/utils',
2828
'#nitro-internal-virtual/storage',
29+
// @nuxt/fonts integration - fontless is only used when @nuxt/fonts is present
30+
'fontless',
31+
'unifont',
2932
'tailwindcss',
3033
// postcss packages (transitive deps of tailwindcss peer dep)
3134
'postcss-calc',

client/composables/fetch.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* eslint-disable ts/ban-ts-comment */
2+
// @ts-nocheck - vue-router type recursion causes excessive stack depth
13
import type {
24
DevToolsMetaDataExtraction,
35
OgImageComponent,
@@ -53,7 +55,6 @@ export function fetchPathDebug() {
5355
}
5456

5557
export function fetchGlobalDebug() {
56-
// @ts-expect-error untyped
5758
return useAsyncData<GlobalDebugResponse>('global-debug', () => {
5859
if (!appFetch.value)
5960
return { runtimeConfig: {} as OgImageRuntimeConfig, componentNames: [] }

docs/content/3.guides/5.custom-fonts.md

Lines changed: 30 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -3,114 +3,54 @@ title: Custom Fonts
33
description: Using custom fonts in your OG Images.
44
---
55

6-
To generate images through Satori a font is required, system fonts can't be used. To
7-
avoid issues, the module will use `Inter` font family (`400`, `700`) by default.
6+
To generate images through Satori a font is required, system fonts can't be used.
87

9-
You can customise the font by using the `fonts` in nuxt.config and when defining the image. You can
10-
load fonts directly from Google Fonts (recommended) or use a local font file.
8+
## Using @nuxt/fonts
119

12-
For using non-english fonts you should read [Non-English Locales](/docs/og-image/guides/non-english-locales) guide for
13-
a workaround.
10+
Custom fonts are managed through [@nuxt/fonts](https://fonts.nuxt.com). Install and configure it to use custom fonts in your OG images:
1411

15-
## Loading A Google Font
16-
17-
Google fonts are recommended as their format will always be supported. To use
18-
Google Fonts simply provide the array of fonts you want to use using `${name}:${weight}`.
19-
20-
This will download and cache the font when you first run your app.
12+
```bash
13+
npx nuxi module add @nuxt/fonts
14+
```
2115

2216
```ts [nuxt.config.ts]
2317
export default defineNuxtConfig({
24-
ogImage: {
25-
fonts: [
26-
// will load the Noto Sans font from Google fonts
27-
'Noto+Sans:400',
28-
'Noto+Sans:700',
29-
'Work+Sans:ital:400'
30-
]
31-
}
32-
})
33-
```
34-
35-
Note: Providing your own fonts will disable the default `Inter` font.
18+
modules: ['@nuxt/fonts', 'nuxt-og-image'],
3619

37-
### Google Font API Mirror
38-
39-
If you're in China or the Google APIs are blocked, you can provide your own proxy server that mirrors Google Fonts **in TTF format**.
40-
41-
```ts
42-
export default defineNuxtConfig({
43-
ogImage: {
44-
// Must serve TTF fonts (not WOFF2)
45-
googleFontMirror: 'your-proxy-server.com'
46-
}
20+
fonts: {
21+
families: [
22+
{ name: 'Roboto', weights: [400, 700], global: true },
23+
],
24+
},
4725
})
4826
```
4927

50-
**Important:** The mirror must serve fonts in TTF or OTF format for Satori compatibility. Most public CDNs only serve WOFF2 which won't work.
28+
::note
29+
The `global: true` option is **required** for fonts to be available in OG Image rendering.
30+
::
5131

52-
**Recommended for China:** Instead of using a mirror, use local font files which are more reliable:
32+
See the [@nuxt/fonts integration guide](/docs/og-image/integrations/fonts) for full documentation.
5333

54-
```ts
55-
export default defineNuxtConfig({
56-
ogImage: {
57-
fonts: [
58-
{
59-
name: 'Inter',
60-
weight: 400,
61-
path: '/fonts/Inter-400.ttf',
62-
}
63-
]
64-
}
65-
})
66-
```
67-
68-
## Loading A Local Font File
69-
70-
Local font files must be either `.otf`, `ttf` or `.woff` and be within the `public` directory.
34+
## Using Custom Fonts in Templates
7135

72-
For example, if you have a font file at `public/fonts/OPTIEinstein-Black.otf`, you can load it with the config:
36+
To use your custom fonts within a template, set the `font-family` style:
7337

74-
```ts [nuxt.config.ts]
75-
export default defineNuxtConfig({
76-
ogImage: {
77-
fonts: [
78-
{
79-
name: 'optieinstein',
80-
weight: 800,
81-
// path must point to a public font file
82-
path: '/fonts/OPTIEinstein-Black.otf',
83-
}
84-
],
85-
}
86-
})
38+
```vue [components/OgImage/MyTemplate.vue]
39+
<template>
40+
<div style="font-family: 'Roboto'">
41+
<h1>{{ title }}</h1>
42+
</div>
43+
</template>
8744
```
8845

89-
## Template Custom Fonts
46+
## Non-English Fonts
9047

91-
Sometimes you'll be rendering a custom template that you want to use a custom font with, without
92-
having to load that font for all templates.
48+
For using non-English fonts, read the [Non-English Locales](/docs/og-image/guides/non-english-locales) guide.
9349

94-
In this case, you can use the `fonts` prop on the `defineOgImage` component.
50+
## Font Format
9551

96-
```ts
97-
defineOgImage({
98-
fonts: [
99-
{
100-
name: 'optieinstein',
101-
weight: 800,
102-
path: '/fonts/OPTIEinstein-Black.otf',
103-
}
104-
]
105-
})
106-
```
52+
Different renderers support different font formats. See the [font format compatibility table](/docs/og-image/integrations/fonts#font-format) for details.
10753

108-
## Using A Custom Font In Your Template
54+
## No @nuxt/fonts Installed
10955

110-
To use your custom fonts, within your template you'll need to set the font-family.
111-
112-
```html
113-
<div style="font-family: 'optieinstein'">
114-
<!-- ... -->
115-
</div>
116-
```
56+
If `@nuxt/fonts` is not installed, no fonts will be available for OG image rendering. Install the module to use custom fonts.

docs/content/4.api/3.config.md

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,33 +28,13 @@ Override the compatibility flags.
2828

2929
See the [Renderers](/docs/og-image/renderers) guide to learn more.
3030

31-
### `fonts`
32-
33-
- Type: `InputFontConfig[]`{lang="ts"}
34-
- Default: `['Inter:400', 'Inter:700']`{lang="ts"}
35-
36-
Fonts families to use when generating images with Satori. When not using Inter it will automatically fetch the font from Google Fonts.
37-
38-
See the [Custom Fonts](/docs/og-image/guides/custom-fonts) documentation for more details.
39-
4031
### `zeroConfig`
4132

4233
- Type: `boolean`{lang="ts"}
4334
- Default: `false`
4435

4536
Enable zero runtime mode. See the [Zero Runtime](/docs/og-image/guides/zero-runtime) documentation for more details.
4637

47-
### `googleFontMirror`
48-
49-
- Type: `string`{lang="ts"}
50-
- Default: `undefined`
51-
52-
Specify a custom Google Fonts mirror host (e.g., your own proxy server that serves TTF fonts).
53-
54-
**Note:** The mirror must serve TTF format fonts for Satori compatibility. Most public CDNs (bunny.net, fontsource, etc.) only serve WOFF2 which is not compatible.
55-
56-
**For China users:** We recommend using local font files via the `fonts` option with `path` instead of relying on mirrors. See the [Custom Fonts](/docs/og-image/guides/custom-fonts#loading-a-local-font-file) guide.
57-
5838
### `satoriOptions`
5939

6040
- Type: `SatoriOptions`{lang="ts"}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
title: '@nuxt/fonts'
3+
description: Using @nuxt/fonts for OG Image font management.
4+
---
5+
6+
Nuxt OG Image uses [@nuxt/fonts](https://fonts.nuxt.com) for font management. This is **required** for custom fonts.
7+
8+
## Setup
9+
10+
1. Install `@nuxt/fonts`:
11+
12+
```bash
13+
npx nuxi module add @nuxt/fonts
14+
```
15+
16+
2. Configure your fonts in `nuxt.config.ts` with `global: true`:
17+
18+
```ts [nuxt.config.ts]
19+
export default defineNuxtConfig({
20+
modules: ['@nuxt/fonts', 'nuxt-og-image'],
21+
22+
fonts: {
23+
families: [
24+
{ name: 'Inter', weights: [400, 700], global: true },
25+
],
26+
},
27+
})
28+
```
29+
30+
::note
31+
The `global: true` option is **required** for fonts to be available in OG Image rendering. Without it, fonts won't be loaded.
32+
::
33+
34+
3. Use the font in your OG Image template:
35+
36+
```vue [components/OgImage/MyTemplate.vue]
37+
<template>
38+
<div style="font-family: 'Inter'">
39+
<h1>{{ title }}</h1>
40+
</div>
41+
</template>
42+
```
43+
44+
## How It Works
45+
46+
1. **Build time**: Nuxt OG Image resolves your configured font families using the same providers as @nuxt/fonts
47+
2. **Runtime**: Fonts are fetched from provider URLs (e.g., Google Fonts) and cached
48+
3. **No @nuxt/fonts**: If not installed, no fonts are available for rendering
49+
50+
## Provider Configuration
51+
52+
Use any provider supported by @nuxt/fonts:
53+
54+
```ts [nuxt.config.ts]
55+
export default defineNuxtConfig({
56+
fonts: {
57+
families: [
58+
// Google Fonts (default provider)
59+
{ name: 'Roboto', weights: [400, 700], global: true },
60+
61+
// Explicit provider
62+
{ name: 'Inter', provider: 'bunny', weights: [400, 700], global: true },
63+
64+
// Local fonts (must be woff2 format)
65+
{
66+
name: 'MyFont',
67+
src: '/fonts/MyFont.woff2',
68+
weight: 400,
69+
global: true,
70+
},
71+
],
72+
},
73+
})
74+
```
75+
76+
## Font Format
77+
78+
Different renderers support different font formats:
79+
80+
| Format | Satori | Takumi | Chromium |
81+
|--------|--------|--------|----------|
82+
| woff2 ||||
83+
| woff ||||
84+
| ttf ||||
85+
| otf ||||
86+
87+
::note
88+
When using Google Fonts or other providers, @nuxt/fonts automatically fetches the correct format. Format compatibility only matters for local fonts.
89+
::
90+
91+
## Troubleshooting
92+
93+
### Font not rendering
94+
95+
1. Ensure the font has `global: true` set
96+
2. Ensure the font is listed in `fonts.families`
97+
3. For local fonts, ensure format matches your renderer (see table above)
98+
99+
### China/Blocked regions
100+
101+
For regions where Google Fonts is blocked, use local fonts or an alternative provider:
102+
103+
```ts
104+
export default defineNuxtConfig({
105+
fonts: {
106+
providers: {
107+
google: false,
108+
},
109+
families: [
110+
{
111+
name: 'Inter',
112+
src: '/fonts/Inter-Regular.woff2',
113+
weight: 400,
114+
global: true,
115+
},
116+
],
117+
}
118+
})
119+
```

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
"jiti": "catalog:",
129129
"linkedom": "catalog:",
130130
"magic-string": "catalog:",
131+
"magicast": "catalog:",
131132
"mocked-exports": "catalog:",
132133
"nuxt-site-config": "catalog:",
133134
"nypm": "catalog:",

patches/@nuxt__fonts@0.12.1.patch

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
diff --git a/dist/module.mjs b/dist/module.mjs
2+
index 37f0d66799b2abddb85b7e1e1dc1743e927df224..a1b2c3d4e5f6789012345678901234567890abcd 100644
3+
--- a/dist/module.mjs
4+
+++ b/dist/module.mjs
5+
@@ -92,6 +92,10 @@ async function setupPublicAssetStrategy(options = {}) {
6+
renderedFontURLs: /* @__PURE__ */ new Map(),
7+
assetsBaseURL: options.prefix || "/_fonts"
8+
};
9+
+ // Expose context for other modules (e.g., og-image) after all modules are loaded
10+
+ nuxt.hook('modules:done', () => {
11+
+ nuxt.callHook('fonts:public-asset-context', context);
12+
+ });
13+
async function devEventHandler(event) {
14+
const filename = event.path.slice(1);
15+
const url = context.renderedFontURLs.get(event.path.slice(1));

playground/app.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,9 @@
2222
<Footer />
2323
</div>
2424
</template>
25+
26+
<style>
27+
:root {
28+
font-family: 'HubotSans';
29+
}
30+
</style>

0 commit comments

Comments
 (0)