npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vite-plugin-google-fonts

v2.1.0

Published

A simple Vite plugin for optimizing and self-hosting fonts from Google Fonts.

Readme

vite-plugin-google-fonts

Self-host Google Fonts in Vite by downloading the font files at build/dev startup, rewriting the Google CSS to local asset URLs, injecting the stylesheet automatically, and preloading the emitted woff2 files.

This is a thing that I vibecoded for using in a project but now I'm using it on all of my projects that uses Vite. So I decided to publish it.

What it does

  • Downloads the configured Google Fonts and stores them in a local cache.
  • Rewrites @font-face rules to local file paths so Vite handles the assets normally.
  • Injects the generated stylesheet into your app entry automatically.
  • Adds <link rel="preload" as="font"> tags for the emitted font files.
  • Prefers variable fonts when available.
  • Falls back to static weights automatically when a variable font is unavailable.
  • Fails the build immediately if a configured font CSS or font file cannot be downloaded.
  • Optionally scans your source files during build to keep static-weight downloads limited to the weights you actually use.

Install

npm install vite-plugin-google-fonts

Peer requirement:

  • vite >= 4
  • node >= 18

TypeScript note:

  • Canonical Google font family names are now type-checked strictly
  • styles, subsets, and manual weights are validated per family
  • This is a semver-major type change for TS consumers upgrading from the older loose Record<string, ...> API

Quick start

// vite.config.ts
import { defineConfig } from 'vite'
import googleFonts from 'vite-plugin-google-fonts'

export default defineConfig({
  plugins: [
    googleFonts({
      fonts: {
        Inter: {
          variable: '--font-sans',
        },
        JetBrains_Mono: {
          variable: '--font-mono',
        },
      },
    }),
  ],
})

Then use the generated CSS variables or font names anywhere in your app:

:root {
  color-scheme: light;
}

body {
  font-family: var(--font-sans);
}

code,
pre {
  font-family: "JetBrains Mono", monospace;
}

Generated CSS variables

Each configured family always gets a canonical variable:

  • Inter -> --font-inter
  • Roboto Mono -> --font-roboto-mono

If you also pass variable, that custom property is generated too.

Example:

googleFonts({
  fonts: {
    Inter: {
      variable: '--font-sans',
    },
  },
})

Generates:

:root {
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-inter: 'Inter', system-ui, sans-serif;
}

Configuration

import type { GoogleFontsPluginOptions } from 'vite-plugin-google-fonts'

Additional exports:

import {
  googleFontFamilies,
  isGoogleFontFamily,
  type GoogleFontFamily,
  type GoogleFontSubset,
  type GoogleFontStyle,
  type GoogleFontWeight,
} from 'vite-plugin-google-fonts'

Plugin options

type OptimizedGoogleFontsPluginOptions = {
  cacheDir?: string
  entry?: string | string[]
  base?: string
  optimizeWeights?: true
  fonts: Partial<{
    [K in GoogleFontFamily]: FontFamilyOptions<K, true>
  }>
}

type ManualGoogleFontsPluginOptions = {
  cacheDir?: string
  entry?: string | string[]
  base?: string
  optimizeWeights: false
  fonts: Partial<{
    [K in GoogleFontFamily]: FontFamilyOptions<K, false>
  }>
}

type DynamicGoogleFontsPluginOptions =
  import('vite-plugin-google-fonts').DynamicGoogleFontsPluginOptions

type GoogleFontsPluginOptions =
  | OptimizedGoogleFontsPluginOptions
  | ManualGoogleFontsPluginOptions
  | DynamicGoogleFontsPluginOptions

Per-font options

type FontFamilyOptions<TFamily extends GoogleFontFamily, TOptimize extends boolean> = {
  variable?: `--${string}`
  styles?: Array<GoogleFontStyle<TFamily>>
  subsets?: Array<GoogleFontSubset<TFamily>>
  display?: 'auto' | 'block' | 'swap' | 'fallback' | 'optional'
  fallback?: string
} & (
  TOptimize extends false
    ? {
        weights?: Array<GoogleFontWeight<TFamily>> | 'variable'
      }
    : {
        weights?: never
      }
)

Type safety

  • Font keys must be canonical Google Fonts family identifiers such as Inter or JetBrains_Mono
  • styles, subsets, and weights are derived from metadata for that specific family
  • weights: 'variable' is only accepted for families that support variable downloads
  • When optimizeWeights is omitted or true, weights is removed from the type surface and rejected at runtime too

Example:

googleFonts({
  optimizeWeights: false,
  fonts: {
    Inter: {
      weights: 'variable',
      styles: ['normal', 'italic'],
      subsets: ['latin', 'latin-ext'],
    },
    Recursive: {
      weights: [300, 400, 500],
      styles: ['normal'],
    },
  },
})

Dynamic configs

If you build font configs dynamically, use the helper exports to narrow user input before constructing the config object:

import { googleFontFamilies, isGoogleFontFamily } from 'vite-plugin-google-fonts'

for (const family of googleFontFamilies) {
  console.log(family)
}

const family = process.env.FONT_FAMILY ?? 'Inter'

if (isGoogleFontFamily(family)) {
  // `family` is now narrowed to `GoogleFontFamily`
  console.log(`Using supported family: ${family}`)
}

cacheDir

Directory used for the generated CSS, metadata, and downloaded font files.

  • Default: node_modules/.google-fonts
  • Value is resolved relative to the Vite project root

entry

Application entry file or files that should receive the generated CSS import.

  • Optional
  • Accepts a single path or an array of paths
  • Relative paths are resolved from the Vite project root
  • Absolute paths are also allowed
  • If omitted, the plugin falls back to framework-specific and common Vite entry file names
  • If provided and a file cannot be found, the plugin throws immediately

Example:

googleFonts({
  entry: ['src/main.tsx', 'src/admin.tsx'],
  fonts: {
    Inter: {},
  },
})

optimizeWeights

When true during vite build, the plugin scans your project files for used font weights and narrows static-font downloads to those weights.

  • Default: true
  • Variable fonts are unaffected because they already cover a range of weights in one file
  • When optimizeWeights is true, per-font weights are intentionally omitted from the type surface

Disable it if you want full manual control over static weights:

googleFonts({
  optimizeWeights: false,
  fonts: {
    Roboto: {
      weights: [400, 500, 700],
      styles: ['normal', 'italic'],
    },
  },
})

base

Relative directory inside cacheDir where downloaded font files are stored and where the generated CSS points.

  • Default: 'fonts'
  • Example: base: 'assets/fonts'
googleFonts({
  cacheDir: '.vite/google-fonts',
  base: 'assets/fonts',
  fonts: {
    Inter: {},
  },
})

weights

Controls which font weights are requested for a single family when optimizeWeights: false.

  • weights: 'variable' requests the variable version when Google Fonts supports it
  • weights: [400, 700] requests only static weights supported by that family
  • If weights is omitted, the plugin first tries a variable font and falls back to available static weights automatically

styles

Controls the style variants supported by the selected family.

googleFonts({
  optimizeWeights: false,
  fonts: {
    Inter: {
      weights: 'variable',
      styles: ['normal', 'italic'],
    },
  },
})

subsets

Filters the downloaded @font-face blocks to the requested subsets.

  • Default: ['latin']
  • Example: ['latin', 'latin-ext']
  • Only subsets available for the selected family are accepted by the type surface and runtime validator

If Google Fonts does not return the requested subset labels for a family, the plugin keeps the original CSS instead of emitting an empty stylesheet.

display

Sets the font-display strategy used in the Google Fonts CSS request.

  • Default: 'swap'

fallback

Fallback font stack appended after the configured family.

  • Default depends on detected font category:
    • sans-serif -> 'system-ui, sans-serif'
    • serif -> 'ui-serif, serif'
    • monospace -> 'ui-monospace, monospace'
    • other categories fall back to 'sans-serif'

Example:

googleFonts({
  fonts: {
    Merriweather: {
      fallback: 'Georgia, serif',
    },
  },
})

Behavior notes

  • The plugin uses local asset URLs, not runtime requests to fonts.googleapis.com.
  • The generated stylesheet is written into the cache directory as google-fonts.css.
  • Font files are cached using content-hashed filenames.
  • The cache is reused across runs and stale files for a family are cleaned up automatically.
  • Automatic entry detection does not inspect index.html.
  • If entry is omitted, the plugin falls back to common framework entry files for React, Vue, TanStack Start, SvelteKit, Solid, Preact, Remix, Qwik, and standard Vite layouts.
  • During build, preload tags point at the final emitted font assets.
  • During dev, preload tags point at the cached files inside your project.

Example setups

Variable-first setup

googleFonts({
  fonts: {
    Inter: {
      variable: '--font-sans',
    },
    Playfair_Display: {
      variable: '--font-serif',
      fallback: 'Georgia, serif',
    },
  },
})

Explicit static weights

googleFonts({
  optimizeWeights: false,
  fonts: {
    Roboto: {
      weights: [400, 700],
      styles: ['normal', 'italic'],
      subsets: ['latin', 'latin-ext'],
    },
  },
})

Limitations

  • Automatic stylesheet injection relies on conventional Vite entry filenames such as main.ts, main.tsx, index.ts, app.tsx, and similar entry modules.
  • Weight optimization is based on static source scanning. Runtime-generated class names or styles cannot be detected.
  • This package currently targets Node-based Vite workflows, not browser-only environments.

Development

npm run generate:catalog
npm run typecheck
npm test
npm run build