Complete guide to managing fonts in Speedwell.
Speedwell uses a streamlined font system that converts TTF fonts to optimized WOFF2 format and automatically configures them for Next.js.
npm run fonts <folder> [fontTypes...]
Compress fonts only:
npm run fonts Switzer
Compress and update font configuration:
npm run fonts Switzer heading heading2 heading3
This will:
src/styles/fonts/Switzer/*.ttf to WOFF2_data/_fonts/_heading.tsx_data/_fonts/_heading2.tsx_data/_fonts/_heading3.tsxAdd your font files to src/styles/fonts/YourFontName/:
src/styles/fonts/
└── Switzer/
├── Switzer-Regular.ttf
├── Switzer-Bold.ttf
├── Switzer-Italic.ttf
└── Switzer-BoldItalic.ttf
# Compress onlynpm run fonts Switzer# Compress and update font config filesnpm run fonts Switzer heading heading2 heading3
_data/_fonts/The system detects font properties from filenames:
| Filename Contains | Weight | CSS Value |
|---|---|---|
Thin | 100 | font-weight: 100 |
ExtraLight | 200 | font-weight: 200 |
Light | 300 | font-weight: 300 |
Regular | 400 | font-weight: 400 |
Medium | 500 | font-weight: 500 |
Semibold | 600 | font-weight: 600 |
Bold | 700 | font-weight: 700 |
Extrabold | 800 | font-weight: 800 |
Black | 900 | font-weight: 900 |
| Filename Contains | Style | CSS Value |
|---|---|---|
Italic | italic | font-style: italic |
FontName-Regular.ttf → weight: 400, style: normal
FontName-Bold.ttf → weight: 700, style: normal
FontName-BoldItalic.ttf → weight: 700, style: italic
FontName-Light.ttf → weight: 300, style: normal
FontName-Semibold.ttf → weight: 600, style: normal
FontName-Variable.ttf → weight: 100-900
Variable fonts support the full weight range in a single file.
Fonts are organized by usage in _data/_fonts/:
| File | Usage | HTML Elements |
|---|---|---|
_heading.tsx | Main headings | <h1> |
_heading2.tsx | Subheadings | <h2>, <h3> |
_heading3.tsx | Small headings | <h4>, <h5>, <h6> |
_body.tsx | Body text | <p>, <span>, etc. |
_accent.tsx | Special text | Custom components |
// _data/_fonts/_heading.tsximport localFont from 'next/font/local'export const heading = localFont({src: [{path: '../../src/styles/fonts/Switzer/Switzer-Regular.woff2',weight: '400',style: 'normal',},{path: '../../src/styles/fonts/Switzer/Switzer-Bold.woff2',weight: '700',style: 'normal',},],variable: '--font-heading',})
Once configured, fonts are applied automatically through CSS variables:
h1 {font-family: var(--font-heading);}h2,h3 {font-family: var(--font-heading2);}h4,h5,h6 {font-family: var(--font-heading3);}body {font-family: var(--font-body);}
import { heading } from '@/_data/_fonts/_heading'export default function Hero() {return <h1 className={heading.className}>Welcome</h1>}
<h1 className="font-heading font-bold">Welcome</h1>
Configure in tailwind.config.js:
module.exports = {theme: {extend: {fontFamily: {heading: ['var(--font-heading)'],heading2: ['var(--font-heading2)'],heading3: ['var(--font-heading3)'],body: ['var(--font-body)'],accent: ['var(--font-accent)'],},},},}
Why WOFF2?
WOFF2 is supported in:
For older browsers, consider adding TTF fallback.
Get your font files from:
mkdir -p src/styles/fonts/MyFontmv MyFont-*.ttf src/styles/fonts/MyFont/
npm run fonts MyFont heading heading2 body
Check that files were created:
src/styles/fonts/MyFont/
├── MyFont-Regular.woff2
├── MyFont-Bold.woff2
└── ...
_data/_fonts/
├── _heading.tsx
├── _heading2.tsx
└── _body.tsx
npm run dev
Visit your site and inspect elements to verify fonts loaded.
Next.js automatically optimizes font loading:
export const heading = localFont({src: './fonts/heading.woff2',display: 'swap', // Show fallback while loadingpreload: true, // Load immediatelyvariable: '--font-heading',})
src/styles/fonts/
Switzer/ # Font family folder
Switzer-Regular.ttf
Switzer-Bold.ttf
...
Geist/
GeistVF.ttf
...
_data/_fonts/ # Generated configs
_heading.tsx
_heading2.tsx
_body.tsx
...
Check:
npm run fonts completed successfullyPossible causes:
ttf2woff2 dependencySolution:
npm install ttf2woff2 --save-dev
Fix filename:
# Wrongmv Font-Reg.ttf Font-Regular.ttf# Wrongmv Font-B.ttf Font-Bold.ttf
Weight detection requires specific keywords in filename.
Edit _data/_fonts/_heading.tsx directly:
import localFont from 'next/font/local'export const heading = localFont({src: [{path: '../../src/styles/fonts/Custom/Custom-Light.woff2',weight: '300',style: 'normal',},{path: '../../src/styles/fonts/Custom/Custom-Regular.woff2',weight: '400',style: 'normal',},{path: '../../src/styles/fonts/Custom/Custom-Bold.woff2',weight: '700',style: 'normal',},],variable: '--font-heading',display: 'swap',preload: true,})
For smaller file sizes, subset fonts to include only needed characters:
# Using pyftsubset (install fonttools)pyftsubset font.ttf \--output-file=font-subset.ttf \--unicodes=U+0020-007F,U+00A0-00FF# Then convert to WOFF2npm run fonts SubsetFont heading