Closed JulianDueck closed 1 week ago
Hey there, @JulianDueck! I'm here to help you with any issues you're facing in your project. Feel free to ask me anything while you wait for a human maintainer to join the conversation.
To resolve the issue of your custom font not loading after upgrading to Nuxt 3.12.1 and restructuring your project's file structure, ensure that the path to your custom font is correct and that the font file is located in the appropriate directory.
Based on the provided nuxt.config.ts
file, your custom font MyCustom
is expected to be located at /public/custom-font.woff2
and CustomGlobal
at /public/font-global.woff2
. Make sure these font files are indeed in the public
directory of your project.
Here is the relevant part of your nuxt.config.ts
:
export default defineNuxtConfig({
devtools: { enabled: true },
modules: ['@nuxt/fonts', '@nuxtjs/tailwindcss', '@unocss/nuxt'],
unocss: {
disableNuxtInlineStyle: false,
},
fonts: {
providers: {
custom: '~/providers/custom',
},
families: [
{ name: 'MyCustom', src: '/custom-font.woff2' },
{ name: 'CustomGlobal', global: true, src: '/font-global.woff2' },
],
},
})
Ensure that the font files are correctly placed in the public
directory:
your-project/
├── public/
│ ├── custom-font.woff2
│ └── font-global.woff2
├── nuxt.config.ts
└── ...
Additionally, the src/module.ts
file in the Nuxt fonts module handles the setup and resolution of font providers. Ensure that your custom fonts are being correctly registered by this module. Here is a snippet from src/module.ts
:
import { addBuildPlugin, addTemplate, defineNuxtModule, resolveAlias, resolvePath, useNuxt } from '@nuxt/kit'
import jiti from 'jiti'
import type { ResourceMeta } from 'vue-bundle-renderer'
import { join, relative } from 'pathe'
import { withoutLeadingSlash } from 'ufo'
import local from './providers/local'
import google from './providers/google'
import bunny from './providers/bunny'
import fontshare from './providers/fontshare'
import adobe from './providers/adobe'
import fontsource from './providers/fontsource'
import { FontFamilyInjectionPlugin, type FontFaceResolution } from './plugins/transform'
import { generateFontFace } from './css/render'
import type { GenericCSSFamily } from './css/parse'
import { setupPublicAssetStrategy } from './assets'
import type { FontFamilyManualOverride, FontFamilyProviderOverride, FontProvider, ModuleHooks, ModuleOptions } from './types'
import { setupDevtoolsConnection } from './devtools'
import { logger } from './logger'
export default defineNuxtModule<ModuleOptions>({
meta: {
name: '@nuxt/fonts',
configKey: 'fonts',
},
defaults: {
devtools: true,
experimental: {
processCSSVariables: false,
},
defaults: {},
assets: {
prefix: '/_fonts',
},
local: {},
google: {},
adobe: {
id: '',
},
providers: {
local,
adobe,
google,
bunny,
fontshare,
fontsource,
},
},
async setup(options, nuxt) {
// Skip when preparing
if (nuxt.options._prepare) return
// Custom merging for defaults - providing a value for any default will override module
// defaults entirely (to prevent array merging)
const normalizedDefaults = {
weights: (options.defaults?.weights || defaultValues.weights).map(v => String(v)),
styles: options.defaults?.styles || defaultValues.styles,
subsets: options.defaults?.subsets || defaultValues.subsets,
fallbacks: Object.fromEntries(Object.entries(defaultValues.fallbacks).map(([key, value]) => [
key,
Array.isArray(options.defaults?.fallbacks) ? options.defaults.fallbacks : options.defaults?.fallbacks?.[key as GenericCSSFamily] || value,
])) as Record<GenericCSSFamily, string[]>,
}
if (!options.defaults?.fallbacks || !Array.isArray(options.defaults.fallbacks)) {
const fallbacks = (options.defaults!.fallbacks as Exclude<NonNullable<typeof options.defaults>['fallbacks'], string[]>) ||= {}
for (const _key in defaultValues.fallbacks) {
const key = _key as keyof typeof defaultValues.fallbacks
fallbacks[key] ||= defaultValues.fallbacks[key]
}
}
const providers = await resolveProviders(options.providers)
const prioritisedProviders = new Set<string>()
// Allow registering and disabling providers
nuxt.hook('modules:done', async () => {
await nuxt.callHook('fonts:providers', providers)
const setups: Array<void | Promise<void>> = []
for (const key in providers) {
const provider = providers[key]!
if (options.providers?.[key] === false || (options.provider && options.provider !== key)) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete providers[key]
}
else if (provider.setup) {
setups.push(provider.setup(options[key as 'google' | 'local' | 'adobe'] || {}, nuxt))
}
}
await Promise.all(setups)
for (const val of options.priority || []) {
if (val in providers) prioritisedProviders.add(val)
}
for (const provider in providers) {
prioritisedProviders.add(provider)
}
})
const { normalizeFontData } = setupPublicAssetStrategy(options.assets)
const { exposeFont } = setupDevtoolsConnection(nuxt.options.dev && !!options.devtools)
async function resolveFontFaceWithOverride(fontFamily: string, override?: FontFamilyManualOverride | FontFamilyProviderOverride, fallbackOptions?: { fallbacks: string[], generic?: GenericCSSFamily }): Promise<FontFaceResolution | undefined> {
const fallbacks = override?.fallbacks || normalizedDefaults.fallbacks[fallbackOptions?.generic || 'sans-serif']
if (override && 'src' in override) {
const fonts = normalizeFontData({
src: override.src,
display: override.display,
weight: override.weight,
style: override.style,
})
exposeFont({
type: 'manual',
fontFamily,
fonts,
})
return {
fallbacks,
fonts,
}
}
// Respect fonts that should not be resolved through `@nuxt/fonts`
if (override?.provider === 'none') {
return
}
// Respect custom weights, styles and subsets options
const defaults = { ...normalizedDefaults, fallbacks }
for (const key of ['weights', 'styles', 'subsets'] as const) {
if (override?.[key]) {
defaults[key as 'weights'] = override[key]!.map(v => String(v))
}
}
// Handle explicit provider
if (override?.provider) {
if (override.provider in providers) {
const result = await providers[override.provider]!.resolveFontFaces!(fontFamily, defaults)
// Rewrite font source URLs to be proxied/local URLs
const fonts = normalizeFontData(result?.fonts || [])
if (!fonts.length || !result) {
logger.warn(`Could not produce font face declaration from \`${override.provider}\` for font family \`${fontFamily}\`.`)
return
}
exposeFont({
type: 'override',
fontFamily,
provider: override.provider,
fonts,
})
return {
fallbacks: result.fallbacks || defaults.fallbacks,
fonts,
}
}
// If not registered, log and fall back to default providers
logger.warn(`Unknown provider \`${override.provider}\` for font family \`${fontFamily}\`. Falling back to default providers.`)
}
for (const key of prioritisedProviders) {
const provider = providers[key]!
if (provider.resolveFontFaces) {
const result = await provider.resolveFontFaces(fontFamily, defaults)
if (result) {
// Rewrite font source URLs to be proxied/local URLs
const fonts = normalizeFontData(result.fonts)
if (fonts.length > 0) {
exposeFont({
type: 'auto',
fontFamily,
provider: key,
fonts,
})
return {
fallbacks: result.fallbacks || defaults.fallbacks,
fonts,
}
}
if (override) {
logger.warn(`Could not produce font face declaration for \`${fontFamily}\` with override.`)
}
}
}
}
}
nuxt.options.css.push('#build/nuxt-fonts-global.css')
addTemplate({
filename: 'nuxt-fonts-global.css',
write: true, // Seemingly necessary to allow vite to process file 🤔
async getContents() {
let css = ''
for (const family of options.families || []) {
if (!family.global) continue
const result = await resolveFontFaceWithOverride(family.name, family)
for (const font of result?.fonts || []) {
// We only inject basic `@font-face` as metrics for fallbacks don't make sense
// in this context unless we provide a name for the user to use elsewhere as a
// `font-family`.
css += generateFontFace(family.name, font) + '\n'
}
}
return css
},
})
const fontMap = new Map<string, Set<string>>()
nuxt.hook('build:manifest', (manifest) => {
function addPreloadLinks(chunk: ResourceMeta, urls: Set<string>) {
chunk.assets ||= []
for (const url of urls) {
chunk.assets.push(url)
if (!manifest[url]) {
manifest[url] = {
file: relative(nuxt.options.app.buildAssetsDir, url),
resourceType: 'font',
preload: true,
}
}
}
}
// CSS files in bundle
for (const id in manifest) {
const chunk = manifest[id]!
if (!chunk.css || chunk.css.length === 0) continue
for (const css of chunk.css) {
const assetName = withoutLeadingSlash(join(nuxt.options.app.buildAssetsDir, css))
if (fontMap.has(assetName)) {
addPreloadLinks(chunk, fontMap.get(assetName)!)
}
}
}
// Source files in bundle
for (const [id, urls] of fontMap) {
const chunk = manifest[relative(nuxt.options.srcDir, id)]
if (!chunk) continue
addPreloadLinks(chunk, urls)
}
})
addBuildPlugin(FontFamilyInjectionPlugin({
dev: nuxt.options.dev,
fontsToPreload: fontMap,
processCSSVariables: options.experimental?.processCSSVariables,
shouldPreload(fontFamily, fontFace) {
const override = options.families?.find(f => f.name === fontFamily)
if (override && override.preload !== undefined) {
return override.preload
}
if (options.defaults?.preload !== undefined) {
return options.defaults.preload
}
return fontFace.src.some(s => 'url' in s) && !fontFace.unicodeRange
},
async resolveFontFace(fontFamily, fallbackOptions) {
const override = options.families?.find(f => f.name === fontFamily)
// This CSS will be injected in a separate location
if (override?.global) {
return
}
return resolveFontFaceWithOverride(fontFamily, override, fallbackOptions)
},
}))
},
})
async function resolveProviders(_providers: ModuleOptions['providers'] = {}) {
const nuxt = useNuxt()
const _jiti = jiti(nuxt.options.rootDir, { interopDefault: true })
const providers = { ..._providers }
for (const key in providers) {
const value = providers[key]
if (value === false) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete providers[key]
}
if (typeof value === 'string') {
providers[key] = await _jiti(await resolvePath(resolveAlias(value)))
}
}
return providers as Record<string, FontProvider>
}
declare module '@nuxt/schema' {
interface NuxtHooks extends ModuleHooks {}
}
If the font files are correctly placed and the issue persists, try clearing the cache and rebuilding your project:
npm run clean
npm run build
npm run start
This should resolve the issue of your custom font not loading [1][2][3].
To continue talking to Dosu, mention @dosu.
I have the same error. But it only occurs in my development environment. Once built, everything works fine.
The path /_fonts/*
returns endpoint 404 to dev.
It never worked for me in development mode, however, after updating to 3.12.1, it won't load in production either.
Would you be able to provide a reproduction? 🙏
This issue was closed because it was open for 7 days without a reproduction.
Hello. I upgraded my project to nuxt 3.12.1 with
npx nuxi@latest upgrade --force
. Enabled this flag innuxt.config.ts
Restructured my projects file structure following this guide. My app functions like before, only that my custom font is not loading. I have a .woff font in
public/
folder, but nuxt is not using it for some reason. Before the changes I made, it worked just fine. I'm using nuxt/fonts 0.7.0