Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Google Fonts Laravel Package

spatie/laravel-google-fonts

Self-host Google Fonts in Laravel with minimal setup: register Google Fonts embed URLs, then use the @googlefonts Blade directive. The package scrapes CSS, downloads and stores font assets locally, and renders CSS inline, falling back to Google links if anything fails.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a deliberately small package: one config file, one Blade directive, one Artisan command. That small surface area is the main reason it's low-risk to adopt — there's no service to run, no database table, no queue worker.

  1. Install and publish the config:
composer require spatie/laravel-google-fonts
php artisan vendor:publish --provider="Spatie\GoogleFonts\GoogleFontsServiceProvider" --tag="google-fonts-config"
  1. Register the embed URL exactly as Google Fonts gives it to you (this is a CSS2 API URL, not a <link> tag — copy the href, not the element):
'fonts' => [
    'default' => 'https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,700;1,400;1,700&display=swap',
],
  1. Drop the directive in your main layout's <head>:
{{-- resources/views/layouts/app.blade.php --}}
@googlefonts
  1. Make the local files reachable and prefetch them so nobody pays the cost on a real request:
php artisan storage:link
php artisan google-fonts:fetch

Where to look first: the published config/google-fonts.php. It is the whole API — fonts, disk, path, inline, preload, fallback, user_agent. Read it top to bottom once and you know everything the package does.

First real use case: a marketing site or SaaS app in the EU where you want to drop the fonts.gstatic.com dependency for privacy/GDPR reasons and shave a DNS lookup + TLS handshake off first paint. Replace the <link> tags in your layout with @googlefonts, prefetch in your deploy pipeline, and you're done in under ten minutes. Secondary use case: multiple typographic registers — a default (Inter for UI) and a code entry (IBM Plex Mono) loaded only on pages that need it via @googlefonts('code').

Implementation Patterns

Named fonts per surface. Don't register ten fonts. Register two or three semantic names and call them only where needed. The directive is cheap when fonts are already local, but each call still emits CSS and each font file is a real request.

'fonts' => [
    'default' => '...family=Inter...',
    'code'    => '...family=IBM+Plex+Mono...',
    'display' => '...family=Fraunces...',   // only on landing pages
],
@googlefonts                          {{-- base layout --}}
@googlefonts('display')               {{-- landing page, pushed into the head via @push --}}

Prefetch in the deploy pipeline, not on first request. This is the pattern that matters operationally. Add to your deploy script after storage:link:

php artisan storage:link
php artisan google-fonts:fetch

Now the scraping and file download happen at deploy time with a warm cache, instead of synchronously inside a user's request. Version 1.6.0 added lazy fetching (only fetching fonts not already present), so re-running fetch on every deploy is cheap and idempotent — safe to put in CI unconditionally.

CSP nonce. If you use spatie/laravel-csp, the inline <style> block will be blocked unless you pass a nonce. Note the two shapes of the directive — positional string or options array, never both:

@googlefonts(['nonce' => csp_nonce()])
@googlefonts(['font' => 'code', 'nonce' => csp_nonce()])

Turn on preload when the font is above the fold. preload => true emits <link rel="preload" as="font"> tags pointing at your local files (fixed in 1.4.4 — earlier versions pointed at Google's URL). It's a genuine LCP win for hero text, but scope it: enabling it globally preloads files for pages that never render them.

CDN / different disk. Set disk to an S3-backed or CDN-fronted disk and path to something like assets/fonts to keep it out of your general public storage. The generated CSS URLs follow the disk's url config, so this works without touching any other setting.

External CSS instead of inline. Setting inline => false emits a <link rel="stylesheet"> to the locally-generated CSS file. Worth it only if you have several fonts and want the stylesheet to be cached across pages; otherwise inline (the default) saves a round trip.

Programmatic URL access. Since PR #21 there's a method to get the URL for a registered font without rendering the directive — useful for preloading from a non-Blade context or generating <link> tags in a custom head partial. Check the class signature in src/ before relying on it, as it's a small, lightly-documented API.

Inertia / Livewire. The directive is a Blade compile-time construct, so it belongs in your root app.blade.php (Inertia) or full-page Blade layout. SPA navigations won't re-run it, which is exactly what you want — fonts load once on the initial document.

Gotchas and Tips

fallback defaults to !env('APP_DEBUG'), which is a double-edged sword. Locally you get an exception when the scrape fails — loud and clear, good. In production you silently get a <link> to Google's servers, which means a privacy-motivated self-hosting setup can quietly regress to calling Google and you'd never know. If the whole point is avoiding Google, log or alert on the fallback path rather than trusting the default.

Cold cache = slow first request. The first rendering of @googlefonts on a fresh environment makes a synchronous HTTP call to fonts.googleapis.com and downloads each .woff2. On a low-traffic box that's one unlucky visitor eating a multi-hundred-millisecond head-of-line block. google-fonts:fetch in the deploy is the fix; treat it as non-optional.

Feature tests will hit the network. If the font files aren't on disk (typical CI), any test that renders your layout triggers real HTTP. Either fake it:

Http::fake([...]); // or a broad Http::fake() to swallow the scrape

or ensure the fetch command runs before the test suite / the files are present. Otherwise you get flaky, slow tests and occasional failures when Google is unreachable.

storage:link and git. Fonts land in storage/app/public/fonts per the default config. That directory is usually gitignored, which is correct for deploy-time fetching but means the link must exist in every environment (including ephemeral containers). If you'd rather commit the fonts for immutable deploys, ensure storage/app/public isn't ignored — but then remember to re-run fetch when you bump a font version.

The user agent is a real decision. The default is a Safari 14 string that makes Google serve WOFF2 only. That's >95% of users, but it means no IE/very old Android. The tradeoff is asymmetric: setting an older UA makes every visitor download heavier fallback formats. Check your actual analytics before "fixing" this for a hypothetical legacy user.

Font versions drift. Google updates font files server-side; your local copies don't update themselves. Plan a periodic refresh (a scheduled google-fonts:fetch, or refresh on deploys at a cadence you're comfortable with) or you'll be shipping a stale Inter for two years without noticing.

Config cache. Nothing unusual here, but fonts is read at directive render time, so after editing config in a cached environment you must php artisan config:cache again. A "my new font isn't loading" report is almost always this, or a missing storage:link.

Extension point: the config disk + path combination is the clean seam for custom storage backends. If you need behaviour the package doesn't offer (e.g. versioned font directories, per-tenant fonts), fork the container binding rather than patching the directive — the package is small enough that a light wrapper is more maintainable than overriding Blade directives.

Scope check: at ~500 stars and a single-maintainer-style Spatie package with a narrow job, this is a "use it and forget it" dependency, not a platform. Keep your integration to config + one directive + one deploy step and you'll never be blocked by its release cadence.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo