On an otherwise perfectly ordinary morning, one of my sites loaded a bit slower than usual, and everything looked just wrong. Headers were suddenly chunky, buttons felt like they’d borrowed a typewriter, and the whole interface had the subtle charm of a 2004 school project.
The culprit? fonts.bunny.net had decided to take the day off.
It’s the kind of outage that doesn’t crash your app, but does make it look like it forgot to brush its teeth. The takeaway: we needed resilience, not panic.
Instead of sprinkling font <link> tags across 50+ templates (multi-tenant platform context), we pulled all font delivery into one place: a Blade component. One knob. One switch. One predictable output.
The new interface
Templates now use:
<x-font-provider families="nunito:400,600,700" />
instead of:
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link href="https://fonts.bunny.net/css?family=nunito:400,600,700" rel="stylesheet" />
The font provider is set through a .env variable, but because it’s a flexible component, we can override the provider and preconnect behavior when needed:
<x-font-provider families="dm-sans:400,600,700" provider="google" :preconnect="true" />
High-level implementation outline
At a high level, the component class normalizes inputs, selects the provider from config, builds the final URL, and optionally renders preconnect tags:
final class FontProvider extends Component
{
public function __construct(
?string $provider = null,
string $families = '',
bool $preconnect = true,
) {
// normalize inputs, select provider from config
}
public function render(): View|Closure|string
{
// build URL + optional preconnect tags
return view('components.font-provider');
}
}
Configuration
Configuration lives in config/app.php so we can switch providers via environment variables:
'fonts' => [
'provider' => env('FONT_PROVIDER', 'bunny'),
'providers' => [
'bunny' => [
'base_url' => 'https://fonts.bunny.net/css',
'preconnects' => [
['url' => 'https://fonts.bunny.net'],
],
'css2' => false,
],
'google' => [
'base_url' => 'https://fonts.googleapis.com/css2',
'preconnects' => [
['url' => 'https://fonts.googleapis.com'],
['url' => 'https://fonts.gstatic.com', 'crossorigin' => true],
],
'css2' => true,
],
],
],
This gives you:
- a default provider (bunny, what else ;-))
- a single place to define base URLs
- provider-specific preconnect domains
- a flag to handle endpoint “dialects” (
cssvscss2)
What to watch out for (Bunny vs Google)
I switched many years back to the Bunny side. Laravel starter kits moved over, I followed, and I never regretted it. Until Bunny briefly reminded me that every external dependency is, at heart, a tiny leap of faith.
Bunny Fonts is a drop-in replacement for Google Fonts, but “drop-in” isn’t quite “identical anymore”:
- Bunny uses the older
cssendpoint with a simplefamily=font:weightsformat. - Google typically uses the
css2endpoint with a more structured grammar:- italics are explicit
- weights use
wght@... - you’ll usually want
display=swap
- Preconnects differ: Google often benefits from a second preconnect to
https://fonts.gstatic.comwithcrossoriginfor best performance.
In other words: same outcome, slightly different dialect. So not the most beautiful code in the blade component, but hey, that is not a big worry.
The happy ending
When fonts.bunny.net takes a coffee break, we don’t scramble. We flip an env value, the component swaps providers, and pages keep their polished look.
No customer reports. No frantic “did we deploy something?” messages. Just one developer quietly muttering, “Please come back, I liked you,” at a CDN.
And when Bunny returns: switch back, move on, and keep the fonts where they belong—quietly doing their job.
(Still crossing my fingers that Bunny stays up. But now, at least, we have a parachute.)