Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: custom fonts with preloading #209

Merged
merged 11 commits into from
Jan 3, 2025
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@astrojs/check": "^0.9.3",
"@astrojs/cloudflare": "^12.0.0-beta.0",
"@astrojs/sitemap": "^3.1.6",
"@fontsource/archivo": "^5.1.0",
"@nanostores/persistent": "^0.10.1",
"@rollup/plugin-graphql": "^2.0.3",
"accept-language-parser": "^1.5.0",
Expand Down
38 changes: 38 additions & 0 deletions src/assets/fonts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// example use of custom fonts. Replace these with your own fonts:
import archivo400url from '@fontsource/archivo/files/archivo-latin-400-normal.woff2?url';
import archivo600url from '@fontsource/archivo/files/archivo-latin-600-normal.woff2?url';

export type Font = {
family: string;
weight: number;
style: string;
woff2Url: string;
}

export const fontFamilyArchivo = 'Archivo, sans-serif';

export const fonts: Font[] = [
{
family: 'Archivo',
weight: 400,
style: 'normal',
woff2Url: archivo400url,
},
{
family: 'Archivo',
weight: 600,
style: 'normal',
woff2Url: archivo600url,
},
];

export const getFontFaceDeclaration = (font: Font) => {
return /* css */`
@font-face {
font-family: '${font.family}';
font-style: ${font.style};
font-weight: ${font.weight};
src: url('${font.woff2Url}') format('woff2');
}
`;
};
15 changes: 15 additions & 0 deletions src/components/PerfHead/PerfHead.astro
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
---
jbmoelker marked this conversation as resolved.
Show resolved Hide resolved
import { datocmsAssetsOrigin, datocmsGraphqlOrigin } from '@lib/datocms';
import { fonts, getFontFaceDeclaration } from '@assets/fonts';

const preConnectOrigins = [datocmsAssetsOrigin, datocmsGraphqlOrigin];
const woff2urls = fonts.map((font) => font.woff2Url);
const fontFaceDeclaration = fonts.map((font) => getFontFaceDeclaration(font)).join('\n');
---

{preConnectOrigins.map((origin) => <link rel="preconnect" href={origin} />)}
{
woff2urls.map((url) => (
<link
rel="preload"
as="font"
type="font/woff2"
href={url}
crossorigin="anonymous"
/>
))
}
<style set:html={fontFaceDeclaration}></style>
40 changes: 40 additions & 0 deletions src/components/PerfHead/PerfHead.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, test, } from 'vitest';
import { renderToFragment } from '@lib/renderer';
import PerfHead from './PerfHead.astro';


describe('PerfHead', async () => {
const fragment = await renderToFragment(PerfHead, { props: {} });

test('pre-connects to origins to speed up following requests', () => {
const links = fragment.querySelectorAll('link[rel="preconnect"]');
expect(links.length).toBeGreaterThanOrEqual(1);
links.forEach((link) => {
// expect link's href to be a valid absolute URL:
const url = new URL(String(link.getAttribute('href')));
expect(url.protocol).toMatch(/https?/);
});
});

test('preloads font files for faster font loading and page rendering', () => {
const links = fragment.querySelectorAll('link[rel="preload"][as="font"]');
expect(links.length).toBeGreaterThanOrEqual(1);
links.forEach((link) => {
expect(link.getAttribute('type')).toBe('font/woff2');
expect(link.getAttribute('href')).toMatch(/\.woff2$/);
expect(link.getAttribute('crossorigin')).toBe('anonymous');
});
});

test('inlines font declarations as critical CSS', () => {
const styleText = fragment.querySelector('style')?.textContent ?? '';
const fontFaceDeclarations = styleText.match(/@font-face\s*{[^}]*}/g) ?? [];
expect(fontFaceDeclarations.length).toBeGreaterThanOrEqual(1);
fontFaceDeclarations.forEach((declaration) => {
// replace HTML entities with their character equivalents:
const cleanedDeclaration = declaration.replace(/&#39;/g, '\'');
// expect font-face declaration to contain woff2 src url:
expect(cleanedDeclaration).toMatch(/src\s*:\s*url\('.*\.woff2'\)\s*format\('woff2'\)/);
});
});
});
8 changes: 8 additions & 0 deletions src/components/PerfHead/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

**Loading instructions for resources on criticial rendering path to improve page loading performance.**

## Features

- Pre-connects to origins to speed up following requests.
- Preloads font files for faster font loading and page rendering.
- Inlines font declarations.

## Relevant links

- [web.dev: Establish network connections early to improve perceived page speed](https://web.dev/articles/preconnect-and-dns-prefetch)
- [web.dev: Preload web fonts to improve loading speed](https://web.dev/articles/codelab-preload-web-fonts)
- [Astro docs: inline CSS imports](https://docs.astro.build/en/guides/styling/#raw-css-imports)
5 changes: 3 additions & 2 deletions src/layouts/Default.astro
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import PreviewModeProvider from '@components/PreviewMode/PreviewModeProvider.ast
import StructuredData from '@components/StructuredData/StructuredData.astro';
import SeoHead from '@components/SeoHead.astro';
import SkipLink from '@components/SkipLink/SkipLink.astro';
import { fontFamilyArchivo } from '@assets/fonts';
import '@assets/a11y.css';

interface Props {
Expand Down Expand Up @@ -82,7 +83,7 @@ const mainContentId = 'content';
</body>
</html>

<style is:global>
<style is:global define:vars={{ fontFamilyArchivo }}>
/* very basic reset */
*,
*::before,
Expand All @@ -93,7 +94,7 @@ const mainContentId = 'content';
body {
margin: 0;
padding: 0;
font-family: sans-serif;
font-family: var(--fontFamilyArchivo);
}
figure {
margin: 0;
Expand Down
Loading