Skip to content

Conversation

@florian-lefebvre
Copy link
Member

@florian-lefebvre florian-lefebvre commented Oct 15, 2024

Summary

Have first-party support for fonts in Astro:

// astro.config.mjs
import { defineConfig, fontProviders } from "astro/config"

export default defineConfig({
	fonts: [
		{
			name: "Roboto",
			cssVariable: "--font-roboto",
			provider: fontProviders.google(),
		}
	]
})
---
// layouts/Layout.astro
import { Font } from "astro:fonts"
---
<head>
	<Font cssVariable="--font-roboto" />
	<style>
		h1 {
			font-family: var(--font-roboto);
		}
	</style>
</head>

Links

@florian-lefebvre florian-lefebvre changed the title [WIP] Fonts Fonts Oct 22, 2024
@florian-lefebvre florian-lefebvre marked this pull request as ready for review October 22, 2024 14:39
@florian-lefebvre florian-lefebvre mentioned this pull request Oct 22, 2024
@lilnasy
Copy link
Contributor

lilnasy commented Oct 22, 2024

How does preloading pick the src when the font provides multiple files for multiple unicode ranges? See IBM Plex Mono on font source for example.

What's the reasoning behind considering subsetting a non-goal? It helps a lot when the font participates in LCP and is trivial to do when using google fonts directly. Moving to astro fonts in that case would be a downgrade.

@florian-lefebvre
Copy link
Member Author

How does preloading pick the src when the font provides multiple files for multiple unicode ranges? See IBM Plex Mono on font source for example.

I think we can't choose the src to pick automatically, so that would preload everything? Not sure, have ideas in mind?

What's the reasoning behind considering subsetting a non-goal? It helps a lot when the font participates in LCP and is trivial to do when using google fonts directly. Moving to astro fonts in that case would be a downgrade.

Only automatic subsetting is a non goal (eg. by analyzing the static content), subsetting is actually supported. I'll clarify the non goal

@florian-lefebvre
Copy link
Member Author

florian-lefebvre commented Apr 24, 2025

Thanks for the feedback!

my app preloads ALL font files, even those of legacy formats like woff

This should not be the case. It was fixed recently, did you try with the latest version of Astro?

or fonts that are rarely used in my app.

That's a good point. I quite like what you're suggesting but that would only work for the local provider. I wonder if there's a way to support it for remote providers, or to move it somewhere else? eg.

---
import { Font } from 'astro:assets';
---

<Font cssVariable="--font-custom" preload={[400,500]} />

It's less centralized but it works for all kinds of providers

@JusticeMatthew
Copy link

---
import { Font } from 'astro:assets';
---

<Font cssVariable="--font-custom" preload={[400,500]} />

Big fan of this

@jonasgeiler
Copy link

jonasgeiler commented Apr 25, 2025

@florian-lefebvre wrote: This should not be the case. It was fixed recently, did you try with the latest version of Astro?

Yeah, I am on [email protected]. I went through the source code to look for the fix you mentioned and why it's not working for me - looks like the fix was only implemented for the non-local providers.

As you can see this code block is in the family.provider !== LOCAL_PROVIDER_NAME branch:
https://github.com/withastro/astro/blob/6ed83606f9bdfdf0a35aef1318cf0e2cb4b4236e/packages/astro/src/assets/fonts/load.ts#L151-L163

But there is no equivalent logic for the family.provider === LOCAL_PROVIDER_NAME branch - the collectPreload argument for collect() is always true:
https://github.com/withastro/astro/blob/6ed83606f9bdfdf0a35aef1318cf0e2cb4b4236e/packages/astro/src/assets/fonts/load.ts#L107


@florian-lefebvre wrote:

---
import { Font } from 'astro:assets';
---

<Font cssVariable="--font-custom" preload={[400,500]} />

This looks like a pretty good solution! The only question is if it should be differentiated between different font-style like normal, italic and oblique.
Maybe allow either a number | string (just the weight) or an object like { weight: number | string, style: 'normal' | 'italic' | 'oblique' } with the weight and style in the preload property array. 🤔

So, together, the type for the preload property could be:

type FontProps = {
  // ...
  preload: (number | string | { weight: number | string, style: 'normal' | 'italic' | 'oblique' })[]
  // ...
};

Which, once implemented, would make it pretty flexible:

---
import { Font } from 'astro:assets';
---

<Font cssVariable="--font-custom" preload={[ 400, { weight: 500, style: 'normal' } ]} />

Just my suggestion!

@florian-lefebvre
Copy link
Member Author

looks like the fix was only implemented for the non-local providers.

You're right! I have a fix on another branch so that should land soon-ish

@StrCode
Copy link

StrCode commented Apr 25, 2025

Hello,
Just took the new fonts feature for a spin, and it's looking fantastic overall!

I did run into an issue when trying to use fonts from Fontshare. There's an issue with the URL for the Fontshare CDN (it looks malformed in the error message), as it's consistently throwing an error when trying to load a font. You can see the error in the screenshot below:

Screenshot 2025-04-25 at 13 38 06

Minimal reproduction: https://congenial-couscous-4jpj9qg4gp42jv6v.github.dev/

@florian-lefebvre
Copy link
Member Author

Hi, thanks for trying it out! Can you open an issue at https://github.com/withastro/astro/issues instead? Thanks!

@florian-lefebvre
Copy link
Member Author

If you are using the local provider, I'd like your feedback on withastro/astro#13640!

@Pkcarreno
Copy link

I have the same request as @tanishqmanuja, my use case is to generate OG images with Satori

What I usually do is to read the .woff file from node_modules

	const fontData = fs.readFileSync(
		'node_modules/@fontsource/ia-writer-duo/files/ia-writer-duo-latin-400-normal.woff'
	)

and then integrate it in Satori like this

const raw = await satori(htmlContent as React.ReactNode, {
		width,
		height,
		fonts: [
			{
				name: 'IA Writer Duo',
				data: fontData,
				style: 'normal',
				weight: 400
			}
		]
	})

I would appreciate a way to achieve this with this feature and thus not depend on extra installations (as in this case manually install a font from fontsource)

Thanks 🙌

@florian-lefebvre
Copy link
Member Author

I plan to look into this in the next 2 weeks!

@florian-lefebvre
Copy link
Member Author

Hi there! I'm working on a way to retrieve URLs, give it a try and share your experience on the PR: withastro/astro#13811

@florian-lefebvre
Copy link
Member Author

Hi there! I'm working on a way to retrieve URLs, give it a try and share your experience on the PR: withastro/astro#13811

cc @tanishqmanuja and @Pkcarreno

@florian-lefebvre
Copy link
Member Author

Hi there! I'm working on a way to have more granular preloads, give it a try and share your experience on the PR: withastro/astro#13826

cc @jonasgeiler

@JusticeMatthew
Copy link

It would be great to have clearer filenames in the console warnings, as this would make it easier to distinguish which fonts are the cuplrits

image

@florian-lefebvre
Copy link
Member Author

I think in builds we should keep them as is as it's better for caching but in dev, we could either do something like inter-500-italic-<hash>.<ext> or even inter-500-italic.<ext>

@JusticeMatthew
Copy link

Agreed with the dev/build

I'm fine with either naming convention, the hashes being there doesn't bother me as long as the name is there too 👍

@JusticeMatthew
Copy link

JusticeMatthew commented Jul 14, 2025

I think in builds we should keep them as is as it's better for caching but in dev, we could either do something like inter-500-italic-<hash>.<ext> or even inter-500-italic.<ext>

Perhaps after what we learned in #13826 it might also be good to indicate if the file is a subset as well? (if possible) 😅

@florian-lefebvre
Copy link
Member Author

@JusticeMatthew see withastro/astro#14279

@robinlahtinen
Copy link

The experimental Fonts API inserts elements at the beginning of the <head> section, which pushes important meta tags such as the charset declaration beyond the first 1024 bytes of the HTML. As a result, Lighthouse reports: "Charset declaration is missing or occurs too late in the HTML."

@delucis
Copy link
Member

delucis commented Sep 14, 2025

The experimental Fonts API inserts elements at the beginning of the <head> section, which pushes important meta tags such as the charset declaration beyond the first 1024 bytes of the HTML. As a result, Lighthouse reports: "Charset declaration is missing or occurs too late in the HTML."

IIUC the <Font> component renders where it is used, so adding it to your pages below important meta tags like that should fix this.

@florian-lefebvre
Copy link
Member Author

Yes as Chris said you control the position in the head

@florian-lefebvre
Copy link
Member Author

What do you think about cssVariable requiring the leading --? Do you think it's fine as is or would you prefer not having to specify it? Eg.

{
-	cssVariable: '--my-font'
+	cssVariable: 'my-font'
}

-<Font cssVariable="--my-font" />
+<Font cssVariable="my-font" />
:root {
	font-family: var(--my-font);
}

@JusticeMatthew
Copy link

What do you think about cssVariable requiring the leading --? Do you think it's fine as is or would you prefer not having to specify it? Eg.

{
-	cssVariable: '--my-font'
+	cssVariable: 'my-font'
}

-<Font cssVariable="--my-font" />
+<Font cssVariable="my-font" />
:root {
	font-family: var(--my-font);
}

Personally, I don't mind either way, but for consistency, I think it would be nice to require the --. This way, users are typing the same thing in each of the few places they need it vs "Oh wait I'm in the component prop, I need to not include the --" if that makes sense

@RomanHauksson
Copy link

Agreed on requiring the --. This made it quicker for me to understand what corresponds to what.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.