博客 / 詳情

返回

Next.js Font 組件怎麼玩?最佳實踐分享

大家好,我是長林啊!一個愛好 JavaScript、Go、Rust 的全棧開發者和 AI 探索者;致力於終生學習和技術分享。

本文首發在我的微信公眾號【長林啊】,歡迎大家關注、分享、點贊!

前面的文章介紹了 Script 組件和 Link 組件,今天我們來看看 Font 組件。在 Web 開發中,字體是一個常常被忽視但至關重要的元素。它不僅影響網站的可讀性和用户體驗,還體現了品牌的個性和風格。選擇合適的字體,可以提高文本的可讀性,幫助用户更好地理解內容,並傳達品牌的獨特氣質。良好的字體選擇不僅可以改善網站的美觀度,還可以吸引用户在網站上停留更長時間。另外,選擇可縮放且適應不同屏幕的字體,也是確保網站在各種設備上保持良好可讀性的關鍵。同時,使用輕量級 Web 字體還可以加快頁面加載速度,提高網站的性能。

傳統字體

在Web設計中,字體是實現視覺表達和用户體驗的關鍵,但與印刷設計不同,Web設計不受物理尺寸或顏色數量的限制,但帶寬成本是一個不容忽視的因素。在 Web 上實現豐富的排版體驗時,字體的使用尤為突出。傳統的 Web 字體要求每個樣式(如常規、粗體、斜體)都需要單獨的字體文件,這可能導致頁面加載時間增加和用户體驗下降。例如,僅包含常規和粗體樣式以及它們對應的斜體,字體數據可能達到好幾百 kb 或更多的字體數據。

傳統字體的使用很方便,通過 @font-face 指定一個自定義的字體,字體文件可以本地文件或者遠程文件,如果提供了 local() 函數,從用户本地查找指定的字體名稱,並且找到了一個匹配項,本地字體就會被使用。否則,字體就會使用 url() 函數下載的資源。然後使用 font-family 來標識“系列”,比如:

@font-face {
    font-family: "Open Sans";
    src:
        url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"),
        url("/fonts/OpenSans-Regular-webfont.woff") format("woff");
}

具體的使用方法如下:

<html lang="en">

<head>
    <title>Web Font Sample</title>
    <style type="text/css" media="screen, print">
        @font-face {
            font-family: "Bitstream Vera Serif Bold";
            src: url("https://mdn.github.io/css-examples/web-fonts/VeraSeBd.ttf");
        }

        body {
            font-family: "Bitstream Vera Serif Bold", serif;
        }
    </style>
</head>

<body>
    This is Bitstream Vera Serif Bold.
</body>

</html>

效果如下:

可變字體

來自iconfont的阿里媽媽靈動體

在可變字體出現之前,每個不同的字體風格都需要單獨的文件,這限制了設計師在 Web 上使用豐富字體的能力。可變字體允許在一個單一的文件中包含多個字體風格(如上圖),從而減少了文件大小和請求數量,提升了頁面加載速度和性能。想了解更多的可變字體相關的內容,可以閲讀《Introducing OpenType Variable Fonts》;當然也可以看 大漠_w3cpluscom 大佬在掘進上的專欄《現代 CSS》的 Web 上的可變字體。

在 Web 上使用可變字體可以分以下幾個步驟。

  • 步驟一:獲取可用的可變字體。
  • 步驟二:在 CSS 中集成可變字體。
  • 步驟三:找出可變字體的軸和範圍值。
  • 步驟四:設置可變字體樣式。
  • 步驟五:降級處理。

在 Google Fonts 可以查看到很多可變字體,篩選條件也很簡單(選中左側的 Variable),如下圖:

每個字體的右上角也可以區分哪些字體是可變字體:

選中一個可變字體之後,進去後可以調整可變字體的版本,然後點擊右上角的 “Get font”按鈕。如下圖:

可以直接獲取代碼或者下載字體文件:

如果選擇以遠程字體文件引入的方式,則點擊 “Get Embed code”後,就可以選擇對應的平台的引入代碼,如下圖:
QQ_1739785067108

整體示例如下:

<html lang="en">

<head>
    <title>Web Font Sample</title>
    <link href="https://fonts.googleapis.com/css2?family=Oswald:wght@200;300;400;469;500;600;681;700&display=swap"
        rel="stylesheet">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wdth,wght@0,94,280;1,94,280&display=swap"
        rel="stylesheet">
    <style type="text/css" media="screen, print">
        @font-face {
            font-family: "Bitstream Vera Serif Bold";
            src: url("https://mdn.github.io/css-examples/web-fonts/VeraSeBd.ttf");
        }

        body {
            font-family: "Bitstream Vera Serif Bold", serif;
        }

        .custom-font {
            font-family: 'Oswald';
            font-weight: 681;
            color: red;
        }

        .roboto-font {
            font-family: "Roboto", serif;
            font-optical-sizing: auto;
            font-weight: 400;
            font-style: normal;
            font-variation-settings:
                "wdth" 494;
        }
    </style>
</head>

<body>
    This is Bitstream Vera Serif Bold.
    <p class="custom-font">
        This is Oswald.
    </p>
    <p class="roboto-font">Whereas recognition of the inherent dignity
    </p>
</body>

</html>

效果如下圖:

有了上面的理論基礎後,按照前面的慣例,在進入正題之前,我們先來準備一下相關的環境,創建一個項目便於演示後面的內容。

使用命令 npx create-next-app@latest --use-pnpm 創建一個新的項目;具體的項目配置選項如下:

在 VS Code 打開以後,項目的依賴如下圖:

我們下面就來看看 Next.js 做了哪些優化?在 Next.js 中怎麼用可變字體?

Font 組件

如果將上面的傳統字體的示例代碼遷移到 Next.js 項目中,在 Next.js 中的引入方式有:

  • 通過 <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wdth,wght@0,88,280;1,88,280&display=swap" rel="stylesheet">
  • 通過 css 文件的方式引入,比如: import "./global.css";,在 global.css 文件中使用 @import url() 引入。
  • 通過內置組件 next/font 包的 localFont 加載或者 Google Font 系列字體來加載。

可變字體在 Next.js 中也比較方便,Next.js 內置了 next/font 組件,幫助我們更好的管理和使用字體,而且 Next.js 官方還做了不少優化,其中就包括佈局偏移的問題。next/font 具體又分為 next/font/googlenext/font/local,分別對應使用 Google 字體和使用本地字體。

Google 字體

// 加載 Google Font 系列的字體
import { Inter, Roboto_Mono } from 'next/font/google'

export const inter = Inter({
    subsets: ['latin'],
    display: 'swap',
})

export const roboto_mono = Roboto_Mono({
    subsets: ['latin'],
    display: 'swap',
})
上面這段字體加載的代碼中,需要注意的是:如果字體是多單詞,使用下劃線 _ 連接,比如 Roboto Mono,導入的時候寫成 Roboto_Mono

最終渲染後的效果如下:
QQ_1739788489179
Next.js 推薦使用可變字體來獲得最佳的性能和靈活性。如果不能使用可變字體,你需要聲明 weight(字重,是指字體的粗細程度):

import { Roboto } from 'next/font/google'

const roboto = Roboto({
    weight: '400',
    subsets: ['latin'],
    display: 'swap',
})

export default function RootLayout({
    children,
}: {
    children: React.ReactNode
}) {
    return (
        <html lang="en" className={roboto.className} >
            <body>{children} </body>
        </html>
    )
}

上面這段代碼中,需要注意 subsets 這個屬性,谷歌的字體是可以指定子集(subset)的,目前 Next.js 官方網站上也沒有具體指出哪些字體支持那些子集,可以通過 next.js 源碼搜字體查看,在 packages/font/src/google/index.ts 中;還可以隨便寫一個,然後通過 TS 的類型推導校正,如下圖:

本地字體

  • 多字體的加載方式

    import localFont from 'next/font/local';
    
    const roboto = localFont({
        src: [
            {
                path: './Roboto-Regular.woff2',
                weight: '400',
                style: 'normal',
            },
            {
                path: './Roboto-Italic.woff2',
                weight: '400',
                style: 'italic',
            },
            {
                path: './Roboto-Bold.woff2',
                weight: '700',
                style: 'normal',
            },
            {
                path: './Roboto-BoldItalic.woff2',
                weight: '700',
                style: 'italic',
            },
        ],
    })
    
    export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
        return (
            <html lang="en" className={roboto.className}>
                <body>{children}</body>
            </html>
        )
    }
  • 單字體加載方式

    import localFont from 'next/font/local'
    
    const manrope = localFont({
        src: './fonts/Manrope.woff2',
        display: 'swap',
    })
    
    export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
        return (
            <html lang="en" className={manrope.className}>
                <body>{children}</body>
            </html>
        )
    }

字體函數的相關參數

上面介紹了 Google 字體和本地字體的相關用法和注意點,接下來看看字體函數的相關參數。

屬性 font/google font/local 類型 必傳
src 字符串或對象數組
weight 字符串或數組 可選
style 字符串或數組 -
subsets 字符串數組 -
axes 字符串數組 -
display 字符串 -
preload 布爾 -
fallback 字符串數組 -
adjustFontFallback 布爾或者字符串 -
variable 字符串 -
declarations 對象數組 -
  • src

    這個屬性只有在 next/font/local 中必傳,它可以是一個字符串,也可以是對象數組,其類型為:Array<{path: string, weight?: string, style?: string}>

    如果是字符串的話,路徑地址相當於字體加載函數調用的位置;比如:比如上面帶代碼 app/page.tsx 中使用 src:'./fonts/Manrope.woff2' 調用字體加載函數,Manrope.woff2 就要放置在 app/fonts/ 下。

  • weight

    字體字重,類似於 font-weight。如果是可變字體,則非必傳,如果不是可變字體,則必傳。它的值可以是字符串或字符串數組:

    • 單個字符串: weight: '400'weight: '100 900'(表示可變字體取值範圍為 100 ~ 900)。
    • 字符串數組:weight: ['100', '400', '900'](表示不可變字體的三個字重值)。
  • style

    字體風格,類似於 font-style。一共有三個可選值:italicobliquenormal;默認值是:normal

    如果使用 next/font/google 的非可變字體,也可以傳入一組樣式值,如 style: ['italic','normal']

    它的值同樣可以是字符串或字符串數組:

    • 單個字符串:style: 'italic' 或者 style: 'oblique' 或者 style: 'normal'
    • 字符串數組:style: ['italic', 'normal']
  • subsets

    這個屬性在上面也有使用過,字體子集(font subsets)可以通過一個字符串數組來定義,數組中包含每個子集的名稱。通過子集指定的字體將在 preload 選項為 true 時(默認值)在 HTML 頭部注入一個預加載屬性的 link 標籤。如果 preloadtrue,但不指定子集會有警告。有些字體只有一個默認子集,比如 latin,也需要手動制定。

    preload 選項默認為 true,如果你不想預加載字體,可以將其設置為 false

    使用方式也比較簡單:

    import { Roboto } from 'next/font/google'
    
    const roboto = Roboto({
        weight: '400',
        subsets: ['latin'],
        display: 'swap',
    })
  • axes

    可變字體(Variable Fonts)通過內置的多種變形軸(axis 的複數形式 axes),允許用户動態調整字體的視覺特性。以字寬(Width)、字重(Weight)、傾斜(Slant)等為代表的變形軸,本質上是對字體形態的數字化控制參數,用户可通過調節這些軸值,使單一字體文件呈現出傳統多款獨立字體的效果。

    比如,Inter 字體,不僅支持基礎的「字重軸」(調節筆畫粗細)和「字寬軸」(調節字符橫向比例),還包含「傾斜軸」(Slant,模擬斜體效果)或「光學尺寸軸」(Optical Size,適配不同顯示環境)等參數,從而通過多維度調整實現更靈活的排版表現。這種技術將傳統靜態字體的離散樣式轉化為連續可變的動態系統,顯著提升了設計效率與視覺一致性。

上圖的 ital 是斜體(italic)、opsz 是光學尺寸軸(Optical Size)、wght 是字重(wight)。

axes 的值是一個字符串數組形式,比如 axes: ['slnt', 'wght'],你可以在 Google 可變字體頁面查詢字體的 Axes 有哪些(如上圖)。默認情況下,只有 weight 軸會被留下(只留一個的目的是減少字體文件大小),如果需要其他的軸就需要單獨聲明。

  • display

    字體顯示策略,類似於(font-display);用於控制頁面加載時 Web Fonts 引起的佈局抖動和偏移的優化策略。它的可選值有 'auto''block''swap''fallback''optional', 在 Next.js 的 next/font 組件中的默認值是 swap,在 Web CSS 中 font-display 的默認值是 auto

    • font-display: auto:使用瀏覽器的預設值,一般是 block
    • font-display: block 會使文本在網頁字體加載期間隱藏(最長3秒),超時後自動用備用字體顯示,待網頁字體加載完成後再切換回原字體。
    • font-display: swap 會立即用備用字體顯示文本(無加載等待),待網頁字體加載完成後自動切換。優勢是內容即時可見,但需確保備用字體與目標字體形態相近,以減少切換時的佈局抖動或偏移的風險。
    • font-display: fallback:先顯示空白(大約 100ms),然後切換為備用字體,時間大概是 3s,3s 內能加載完字體,就使用字體,3s 內加載不完,後續接着使用備用字體。
    • font-display: optional:先顯示空白(大約 100ms),100ms 內能加載完就用,加載不完就直接使用備用字體。

    在 Next.js 的 next/font 組件中的默認值是 swap

  • preload

    用於指定是否預加載該字體,默認值為 true(啓用預加載)。

  • fallback

    當字體加載失敗時使用的備用字體列表;該值為一個字符串數組:fallback: ['system-ui', 'arial'],沒有默認值。

  • adjustFontFallback

    對於 next/font/google 一個布爾值,用於設置是否啓用自動備用字體以減少累積佈局偏移(Cumulative Layout Shift),默認值為 true(啓用)。

    對於 next/font/local 一個字符串或布爾值 false,用於設置是否啓用自動備用字體以減少累積佈局偏移。可選值為 'Arial''Times New Roman'false,默認值為 'Arial'

    示例:

    • adjustFontFallback: false:適用於 next/font/google(禁用自動備用字體優化)
    • adjustFontFallback: 'Times New Roman':適用於 next/font/local(手動指定備用字體為 Times New Roman)
  • variable

    一個字符串值,用於定義當通過 CSS 變量方式應用樣式時所使用的 CSS 變量名稱。在 Web CSS 中開發者自定義屬性標記設定值,然後使用 CSS 的 var() 函數來獲取值,比如:.p {--primary-color: #f4500; background-color: var(--primary-color);},這裏就不詳細介紹這塊的內容,如果不熟悉可以閲讀 MDN 的《使用 CSS 自定義屬性(變量)》。

    在 Next.js 中要怎麼聲明和使用一個 CSS 變量呢?就要藉助 variable 屬性。使用方式如下:

    在瀏覽器中解析後的效果如下圖:

    上面只是聲明瞭,如果我們要在 CSS 中使用還需要藉助 var() 函數:

    .title {
      font-family: var(--font-geist-sans);
      font-weight: 300;
      font-style: italic;
    }

    效果如下:

  • declarations

    進一步自定義 @font-face 內容的生成。

    QQ_1739870131503

    除了這些常用的屬性外,還有不少我們開發中不是很常見的屬性,比如 font-variant-ligaturesfont-variant-capsfont-variant-east-asianfont-variant-alternatesfont-variant-numericfont-variant-position 等等;而 declarations 就是給你用來在 next/font/local 時自定義 @font-face 的生成,使用示例如下:

    import localFont from 'next/font/local';
    
    const manrope = localFont({
        src: "./fonts/Manrope-VariableFont_wght.ttf",
        display: 'swap',
        variable: '--manrope',
        fallback: ['ui-sans-serif', 'system-ui'],
        adjustFontFallback: false,
        declarations: [
            { prop: 'font-optical-sizing', value: 'auto' },
            { prop: 'ascent-override', value: '90%' },
            { prop: '-webkit-font-smoothing', value: 'antialiased' },
            { prop: '-moz-osx-font-smoothing', value: 'grayscale' }
        ]
    });

    使用樣式

    在 Next.js 中,可以通過 classNamestyleCSS 變量 三種方式應用字體的樣式。

CSS 變量在前面已經有過演示了,下面就演示下 classNamestyle 的使用。直接在 className 上使用已加載字體的類名和樣式,如下:

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  display: "swap",
  subsets: ["latin"],
  weight: ['100', '200', '400', '500', '600', '700', '800'],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

// ...

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistMono.className} ${geistSans.className} ${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

最佳實踐

多種字體的使用

使用多種字體有兩種方法,一種是導出字體,通過 className 來使用;一種是通過 CSS 變量的形式。

  • 導出字體

    next/font/google 中導出字體,然後添加配置:

    import { Geist, Geist_Mono } from "next/font/google";
    
    const geistSans = Geist({
      variable: "--font-geist-sans",
      display: "swap",
      subsets: ["latin"],
      weight: ['100', '200', '400', '500', '600', '700', '800'],
    });
    
    const geistMono = Geist_Mono({
      variable: "--font-geist-mono",
      subsets: ["latin"],
    });

    在需要的時候導入並使用:

    export default function RootLayout({
      children,
    }: Readonly<{
      children: React.ReactNode;
    }>) {
      return (
        <html lang="en">
          <body
            className={`${geistMono.className} ${geistSans.className} antialiased`}
          >
            {children}
          </body>
        </html>
      );
    }
  • 通過 CSS 變量使用

    前面已經使用過這種方式,這裏也簡單看一下:

    import { Geist, Geist_Mono } from 'next/font/google'
    
    const geistSans = Geist({
      variable: "--font-geist-sans",
      display: "swap",
      subsets: ["latin"],
      weight: ['100', '200', '400', '500', '600', '700', '800'],
    });
    
    const geistMono = Geist_Mono({
      variable: "--font-geist-mono",
      subsets: ["latin"],
    });
    
    export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }) {
      return (
        <html lang="en" className={`${geistSans.variable} ${geistMono.variable}`}>
          <body>
            <h1>My App</h1>
            <div>{children}</div>
          </body>
        </html>
      )
    }

    在瀏覽器渲染後的效果如下:

    從圖中可以看到,聲明瞭 --font-geist-mono--font-geist-sans 兩個 CSS 變量,當需要使用的地方時就可以通過 var() 的形式引用:

    html {
        font-family: var(--font-geist-sans);
    }
    .title{
        font-family: var(--font-geist-mono);
    }

跟 tailwindcss 搭配一起使用

其實這塊本質跟 CSS 變量的引用是一樣的,只不過結合 tailwindcss 時,需要在 tailwind.config.ts 中添加配置!具體的配置如下:

  • 通過 variable 聲明 CSS 變量

    import { Geist, Geist_Mono } from 'next/font/google'
    import './globals.css'  // 引入 css
    
    const geistSans = Geist({
      variable: "--font-geist-sans",
      display: "swap",
      subsets: ["latin"],
      weight: ['100', '200', '400', '500', '600', '700', '800'],
    });
    
    const geistMono = Geist_Mono({
      variable: "--font-geist-mono",
      subsets: ["latin"],
    });
    
    export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }) {
      return (
        <html lang="en" className={`${geistSans.variable} ${geistMono.variable}`}>
          <body>
            <h1>My App</h1>
            <div>{children}</div>
          </body>
        </html>
      )
    }
  • globals CSS 文件

    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  • 將 CSS 變量添加到 tailwind.config.ts 配置中

    fontFamily: {
      sans: ["var(--font-geist-sans)"],
      mono: ["var(--font-geist-mono)"],
    },

    具體配置如下圖:

  • 在頁面中使用

    在 tailwindcss 中自定義字體,需要通過 font- 為前綴來使用!
    <li className='font-sans'>Save and see your changes instantly.</li>

    瀏覽器的效果:

iconfont 字體使用

這裏就引用阿里媽媽方圓體字體,如下圖:

因為篇幅的問題,就不一步步演示字體的下載,下載字體需要登錄!

下載字體後,將字體放入項目中並在 globals.css 中引入,然後創建 alimama/page.tsx 文件。 如下圖:

引入字體的 CSS 代碼:

@font-face {
    font-family: "alimama";
    font-weight: 400;
    src: url("../fonts/AlimamaFangYuanTiVF-Thin.woff2") format("woff2"),
        url("../fonts/AlimamaFangYuanTiVF-Thin.woff") format("woff");
}

接着就在在 tailwind.config.ts 文件中配置阿里媽媽字體:

alimama: ['alimama'],

下面是在 app/alimama/page.tsx 中的使用:

const page = () => {
    return (
        <div className="font-alimama text-3xl">這是阿里媽媽字體</div>
    )
}

export default page

效果如下:

關於 iconfont 圖標的使用,這裏推薦一篇掘金社區的文章 nextjs封裝svgIcon,使用iconfont!

上面引入阿里媽媽字體也可以通過 localFont 函數加載,因為篇幅太長,這裏就不詳細説明了,我在演示項目中完善了這一塊的內容,可以通過 https://github.com/clin211/next-awesome/commit/813b6c809ccd203051d0f6577f856acbb2c92218 查看!

總結

本文對比介紹了傳統字體和可變字體的區別,接着引出 Next.js 中 Font 組件;Next.js 的 Font 組件通過自動預加載消除佈局偏移可變字體支持,顯著提升網頁性能與開發效率,同時賦予設計更大靈活性。其提供 Google 字體與本地託管雙模式加載方案,結合 subsets 子集、axes 可變軸等配置等,構建出高效字體加載體系。實踐中推薦採用 CSS 變量全局管理,優先使用可變字體減少文件體積,配合子集化加載進一步優化性能。通過變量綁定無縫集成 Tailwind 等主流框架,並支持混合字體方案靈活應對多樣化設計場景。開發中需注意本地字體路徑、非可變字體的必填參數(如 weight/style),以及生產環境下預加載等等細節。

「文章推薦」

  • What is My Font?:https://medium.com/@paowens59/what-is-my-font-f775c72fe67a
  • Introducing OpenType Variable Fonts:https://medium.com/variable-fonts/https-medium-com-tiro-intro...
  • 如何在前端項目中使用出色的開源字體:https://juejin.cn/post/7381784676720050215?searchId=202502171...
  • 字體文件 OTF 壓縮:https://juejin.cn/post/7397592619809308691?searchId=202502171...
  • nextjs封裝svgIcon,使用iconfont:https://juejin.cn/post/7326414660487512116

「參考資料」

  • @font-face:https://developer.mozilla.org/zh-CN/docs/Web/CSS/@font-face
  • subset:https://fonts.google.com/knowledge/glossary/subsetting
  • MDN font-weight:https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-weight
  • MDN font-style:https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-style
  • tailwindcss 中自定義字體:https://tailwindcss.com/docs/font-family
  • 字字珠璣:https://github.com/fan2/FontType/blob/master/README.md
user avatar tigerandflower 頭像 chongdianqishi 頭像 codepencil 頭像 mmmy_a 頭像 wjchumble 頭像 shangxindi 頭像 joyerli 頭像 codexu 頭像 notrynosuccess 頭像 zuckjet 頭像 yumiko_5c088de8aa1fe 頭像 yangon 頭像
23 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.