ahmad hasanzadeh branding logoahmad hasanzadeh branding logo
    خانهپروژه هامقالاتدرباره منتکنولوژی هاارتباط با من
ahmad hasanzadeh branding logoahmad hasanzadeh branding logo

ممنون که سر زدی ッ

© ۱۴۰۵

احمد حسن زاده. تمامی حقوق محفوظ است.

کامپوننت فونت Next.js (next/font): راهنمای کامل بهینه‌سازی فونت‌های وب

کامپوننت فونت Next.js (next/font): راهنمای کامل بهینه‌سازی فونت‌های وب

راهنمای جامع next/font در Next.js؛ نحوه استفاده از فونت‌های گوگل و فونت‌های محلی، حذف Layout Shift و بهینه‌سازی کامل عملکرد وب‌سایت.

فونت‌ها یکی از فراموش‌شده‌ترین بخش‌های بهینه‌سازی عملکرد وب هستند. خیلی از تیم‌ها روی تصاویر، جاوااسکریپت و کش کردن API وقت می‌گذارند، اما فونت وب را با یک <link> ساده به Google Fonts متصل می‌کنند و از کنارش رد می‌شوند. نتیجه معمولاً همان چیزی است که همه‌مان تجربه‌اش کرده‌ایم: صفحه لود می‌شود، متن با فونت پیش‌فرض سیستم نمایش داده می‌شود، و یک لحظه بعد، وقتی فونت واقعی از سرور گوگل می‌رسد، کل چیدمان صفحه می‌پرد. این پدیده Cumulative Layout Shift یا CLS نام دارد و یکی از معیارهای اصلی Core Web Vitals گوگل است.

Next.js برای حل دقیق همین مشکل، ماژول next/font را ارائه کرده است. این ماژول فونت‌ها را در زمان build دانلود و به‌صورت خودکار روی همان سرور شما میزبانی (self-host) می‌کند؛ یعنی مرورگر کاربر هیچ درخواستی به سرورهای گوگل ارسال نمی‌کند. در این مقاله قرار است این ابزار را از صفر تا استفاده حرفه‌ای در پروژه‌های واقعی بررسی کنیم.

مقایسه لود فونت با و بدون next/font و تاثیر آن روی Layout Shift

چرا اصلاً به next/font نیاز داریم؟

قبل از اینکه سراغ کد برویم، بهتر است بدانیم این ابزار دقیقاً چه مشکلاتی را حل می‌کند:

  • حذف درخواست‌های خارجی: فایل‌های فونت در زمان build دانلود و در کنار بقیه‌ی فایل‌های استاتیک پروژه شما میزبانی می‌شوند. مرورگر کاربر هیچ‌وقت مستقیماً به fonts.googleapis.com وصل نمی‌شود.
  • حریم خصوصی بهتر: چون درخواستی به سرور گوگل ارسال نمی‌شود، هیچ داده‌ای (مثل IP کاربر) به آن سرورها فرستاده نمی‌شود.
  • حذف Layout Shift: Next.js با محاسبه دقیق ابعاد فونت جایگزین (fallback)، از پرش چیدمان صفحه هنگام لود شدن فونت اصلی جلوگیری می‌کند.
  • عملکرد بهتر: چون فایل فونت از همان دامنه سایت شما (و نه یک دامنه‌ی خارجی) سرو می‌شود، زمان اتصال (connection time) حذف می‌شود.
  • نصب و پیش‌نیازها

    از نسخه‌ی 13.2 به بعد Next.js، دیگر نیازی به نصب پکیج جداگانه نیست. قبلاً این قابلیت با نام @next/font یک پکیج مستقل بود، اما الان مستقیماً داخل خود Next.js قرار دارد و فقط کافی است از next/font/google یا next/font/local ایمپورت کنید.

    استفاده از فونت‌های گوگل با next/font/google

    ساده‌ترین حالت استفاده، ایمپورت مستقیم یک فونت از گوگل و اعمال آن روی layout اصلی پروژه است:

    app/layout.tsxtsx
    import { Inter } from 'next/font/google'
     
    // اگر فونت متغیر (variable) است، نیازی به تعیین weight نیست
    const inter = Inter({
      subsets: ['latin'],
      display: 'swap',
    })
     
    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode
    }) {
      return (
        <html lang="en" className={inter.className}>
          <body>{children}</body>
        </html>
      )
    }

    نکته‌ی مهم: نام‌های فونتی که چند کلمه دارند (مثل Roboto Mono) باید با آندرلاین (_) ایمپورت شوند، یعنی به‌صورت Roboto_Mono.

    اگر فونت شما variable نباشد، حتماً باید مقدار weight را مشخص کنید:

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

    و برای چند وزن یا استایل، از آرایه استفاده می‌کنید:

    app/layout.jsjsx
    const roboto = Roboto({
      weight: ['400', '700'],
      style: ['normal', 'italic'],
      subsets: ['latin'],
      display: 'swap',
    })

    تعیین subset فونت (خیلی مهم و اغلب فراموش می‌شود)

    فونت‌های گوگل به‌صورت خودکار subset (زیرمجموعه‌ی حروف مربوط به یک زبان) می‌شوند تا حجم فایل کاهش پیدا کند. اگر preload فعال باشد (که به‌طور پیش‌فرض هست) و شما subset را مشخص نکنید، Next.js یک هشدار در کنسول نمایش می‌دهد. برای پروژه‌های انگلیسی‌زبان معمولاً کافی است:

    jsx
    const inter = Inter({ subsets: ['latin'] })

    جدول کامل تنظیمات قابل استفاده

    هر دو تابع next/font/google و next/font/local مجموعه‌ای از آپشن‌ها را می‌پذیرند که دانستن دقیق آن‌ها برای هر پروژه‌ای ضروری است:

    • src: مسیر فایل فونت. فقط در next/font/local استفاده می‌شود و اجباری است. می‌تواند یک رشته یا آرایه‌ای از آبجکت‌ها با path، weight و style باشد.
    • weight: وزن فونت. اگر فونت variable نباشد، این مقدار اجباری است. برای فونت‌های variable می‌توانید یک رنج مثل '100 900' بدهید.
    • style: مقدار normal یا italic (برای فونت‌های گوگل) یا هر استایل استاندارد دیگر برای فونت‌های local.
    • subsets: فقط برای next/font/google. آرایه‌ای از زیرمجموعه‌های زبانی که باید preload شوند.
    • axes: محورهای اضافه‌ی یک فونت variable (مثل slnt برای Inter)، فقط برای فونت‌های گوگل.
    • display: مقدار font-display در CSS، با مقادیر auto, block, swap, fallback یا optional. مقدار پیش‌فرض swap است.
    • preload: بولین که مشخص می‌کند فونت preload شود یا نه. پیش‌فرض true است.
    • fallback: آرایه‌ای از فونت‌های جایگزین در صورت شکست لود شدن فونت اصلی.
    • adjustFontFallback: برای فونت‌های گوگل یک بولین است که CLS را کاهش می‌دهد (پیش‌فرض true)؛ برای فونت‌های local می‌تواند 'Arial', 'Times New Roman' یا false باشد.
    • variable: نام یک متغیر CSS برای استفاده در روش CSS Variables.
    • declarations: فقط برای next/font/local؛ آرایه‌ای از توصیفگرهای @font-face مثل ascent-override.

    استفاده از فونت‌های محلی (Local Fonts)

    اگر فونت شما از گوگل نیست (مثلاً یک فونت اختصاصی برند)، از next/font/local استفاده می‌کنید:

    app/layout.tsxtsx
    import localFont from 'next/font/local'
     
    // فایل فونت می‌تواند داخل خود پوشه app قرار بگیرد
    const myFont = localFont({
      src: './my-font.woff2',
      display: 'swap',
    })
     
    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode
    }) {
      return (
        <html lang="en" className={myFont.className}>
          <body>{children}</body>
        </html>
      )
    }

    اگر یک فونت چند فایل مختلف برای وزن‌ها و استایل‌های متفاوت دارد، src را به‌صورت آرایه می‌دهید:

    js
    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' },
      ],
    })

    استفاده از چند فونت در یک پروژه

    در پروژه‌های واقعی معمولاً بیش از یک فونت داریم؛ مثلاً یک فونت برای متن عادی و یک فونت مونواسپیس برای کدها. دو روش استاندارد برای مدیریت این حالت وجود دارد.

    روش اول: فایل utility مجزا. یک فایل جدا برای تعریف فونت‌ها می‌سازید تا هر فونت فقط وقتی واقعاً رندر می‌شود preload شود:

    app/fonts.tsts
    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',
    })

    سپس هرجا لازم بود، همان کلاس را ایمپورت و استفاده می‌کنید:

    app/page.tsxtsx
    import { roboto_mono } from './fonts'
     
    export default function Page() {
      return <h1 className={roboto_mono.className}>My page</h1>
    }

    روش دوم: متغیرهای CSS. به‌جای className، از variable استفاده می‌کنید تا فونت را در قالب یک متغیر CSS تعریف کنید:

    app/layout.tsxtsx
    import { Inter, Roboto_Mono } from 'next/font/google'
     
    const inter = Inter({
      subsets: ['latin'],
      variable: '--font-inter',
      display: 'swap',
    })
     
    const roboto_mono = Roboto_Mono({
      subsets: ['latin'],
      variable: '--font-roboto-mono',
      display: 'swap',
    })
     
    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode
    }) {
      return (
        <html className={`${inter.variable} ${roboto_mono.variable}`}>
          <body>{children}</body>
        </html>
      )
    }

    و در فایل CSS خود:

    app/global.csscss
    html {
      font-family: var(--font-inter);
    }
     
    h1 {
      font-family: var(--font-roboto-mono);
    }

    نکته: بیش از حد از فونت‌های مختلف استفاده نکنید؛ هر فونت جدید یعنی یک فایل دیگر که کاربر باید دانلود کند.

    ادغام next/font با Tailwind CSS

    اگر از Tailwind استفاده می‌کنید (که در اکثر پروژه‌های امروزی همین‌طور است)، ترکیب next/font با متغیرهای CSS بهترین روش است:

    app/layout.tsxtsx
    import { Inter, Roboto_Mono } from 'next/font/google'
     
    const inter = Inter({
      subsets: ['latin'],
      display: 'swap',
      variable: '--font-inter',
    })
     
    const roboto_mono = Roboto_Mono({
      subsets: ['latin'],
      display: 'swap',
      variable: '--font-roboto-mono',
    })
     
    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode
    }) {
      return (
        <html
          className={`${inter.variable} ${roboto_mono.variable} antialiased`}
        >
          <body>{children}</body>
        </html>
      )
    }

    سپس در فایل CSS اصلی خود (برای Tailwind v4):

    global.csscss
    @import 'tailwindcss';
     
    @theme inline {
      --font-sans: var(--font-inter);
      --font-mono: var(--font-roboto-mono);
    }

    اگر هنوز از Tailwind نسخه 3 استفاده می‌کنید، این تنظیمات در tailwind.config.js انجام می‌شود:

    tailwind.config.jsjs
    module.exports = {
      content: [
        './pages/**/*.{js,ts,jsx,tsx}',
        './components/**/*.{js,ts,jsx,tsx}',
        './app/**/*.{js,ts,jsx,tsx}',
      ],
      theme: {
        extend: {
          fontFamily: {
            sans: ['var(--font-inter)'],
            mono: ['var(--font-roboto-mono)'],
          },
        },
      },
    }

    بعد از این تنظیم، به‌راحتی از کلاس‌های font-sans و font-mono استفاده می‌کنید.

    سه روش اعمال استایل فونت

    next/font سه روش برای اعمال فونت روی المان‌ها ارائه می‌دهد:

    • className: ساده‌ترین روش. کلاس تولیدشده را مستقیم روی المان می‌گذارید: <p className={inter.className}>
    • style: یک آبجکت CSS برمی‌گرداند که شامل fontFamily و فونت‌های fallback است: <p style={inter.style}>
    • متغیرهای CSS: برای زمانی که می‌خواهید استایل را در یک فایل CSS خارجی تعریف کنید و آپشن‌های بیشتری اضافه کنید.

    فایل تعریف فونت‌ها (Font Definitions File) برای پروژه‌های بزرگ

    اگر پروژه‌ی شما چند فونت مختلف دارد که در بخش‌های مختلف استفاده می‌شوند، بهتر است یک فایل مرکزی برای تعریف تمام فونت‌ها بسازید تا هر فونت فقط یک بار در کل اپلیکیشن instantiate شود:

    styles/fonts.tsts
    import { Inter, Lora, Source_Sans_3 } from 'next/font/google'
    import localFont from 'next/font/local'
     
    const inter = Inter()
    const lora = Lora()
    const sourceCodePro400 = Source_Sans_3({ weight: '400' })
    const sourceCodePro700 = Source_Sans_3({ weight: '700' })
    const greatVibes = localFont({ src: './GreatVibes-Regular.ttf' })
     
    export { inter, lora, sourceCodePro400, sourceCodePro700, greatVibes }

    سپس در هر جای پروژه، فقط فونت مورد نیاز را ایمپورت می‌کنید:

    app/page.tsxtsx
    import { inter, lora, sourceCodePro700, greatVibes } from '../styles/fonts'
     
    export default function Page() {
      return (
        <div>
          <p className={inter.className}>Hello world using Inter font</p>
          <p style={lora.style}>Hello world using Lora font</p>
          <p className={sourceCodePro700.className}>Hello with weight 700</p>
          <p className={greatVibes.className}>My title in Great Vibes font</p>
        </div>
      )
    }

    برای دسترسی راحت‌تر، می‌توانید در tsconfig.json یک path alias هم تعریف کنید:

    tsconfig.jsonjson
    {
      "compilerOptions": {
        "paths": {
          "@/fonts": ["./styles/fonts"]
        }
      }
    }

    چگونگی رفتار Preloading

    نکته‌ای که خیلی از توسعه‌دهنده‌ها اشتباه متوجه می‌شوند این است: صدا زدن یک تابع فونت به‌معنی preload شدن آن در کل سایت نیست. رفتار دقیق به محل استفاده بستگی دارد:

    • اگر در یک page خاص استفاده شود، فقط همان مسیر preload می‌شود.
    • اگر در یک layout استفاده شود، تمام مسیرهای زیرمجموعه‌ی آن layout preload می‌شوند.
    • اگر در root layout استفاده شود، در کل سایت preload می‌شود.

    چند اشتباه رایج که باید از آن‌ها دوری کنید

    • فراموش کردن subsets هنگام preload فعال، که باعث هشدار در کنسول می‌شود.
    • تعریف یک فونت داخل یک کامپوننت که چندبار رندر می‌شود؛ این کار باعث می‌شود همان فونت چندبار instantiate شود. همیشه فونت‌ها را در سطح ماژول (خارج از کامپوننت) تعریف کنید.
    • استفاده از تعداد زیادی فونت مختلف فقط برای تنوع بصری، بدون توجه به حجم اضافه‌شده به باندل.
    • نادیده گرفتن adjustFontFallback، درحالی‌که این آپشن دقیقاً همان چیزی است که Layout Shift را کاهش می‌دهد.

    جمع‌بندی

    next/font یکی از آن ابزارهایی است که وقتی درست استفاده شود، کاملاً نامرئی است: کاربر هیچ‌وقت متوجه نمی‌شود که فونت شما بهینه شده، فقط حس می‌کند سایت شما سریع‌تر و باثبات‌تر است. برای اکثر پروژه‌ها، شروع با یک فونت variable از Google Fonts همراه با subsets و display: 'swap' کافی است؛ و برای پروژه‌های بزرگ‌تر، ترکیب متغیرهای CSS با Tailwind و یک فایل تعریف فونت مرکزی، بهترین معماری ممکن است.

    ادمین

    نویسنده:

    ادمین
    دقیقه:12
    انتشار :۱۴۰۵/۴/۲۸
    آپدیت :۱۴۰۵/۴/۲۸

    دسته بندی ها

    فرانت‌اند

    تگ ها

    #Next.js#next/font#Web Performance#Google Fonts#Local Fonts#Frontend