As much as a Hugo theme’s aesthetics meets our requirement, sometimes the default fonts do not fit our need. For example, the Lato font that comes with the Gokarna theme doesn’t support Japanese. As a result, the browser falls back to the system serif font for any Japanese character. This makes the viewing experience very jarring and unfriendly.
Being able to use our own fonts gives us the freedom to express and fulfill our design aspiration. In this post, I’m going to show how you can use your own fonts in Hugo, both via embedding a CDN link or hosting it as a static local font.
Use web fonts via CDN
There are many places that serve free fonts via CDN. For example:
In this post, let’s use Google Fonts for simplicity.
Get Google Fonts link
- Go to Google Fonts. For example, I search for “Japanese” in the Search fonts box to find a font that supports Japanese.
- In the font page , scroll down to Styles section. Type your typical text to preview aesthetics and whether the font supports your desired language. Note the desired styles/weights (e.g., Regular 400, Bold 700).
- If everything looks good, click Get font button on the upper right.
- Click Get embed code.
- Copy the code from Embed code in the of your html. For example:
<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=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
The
preconnect
tags help speed up the connection and are highly recommended. Thedisplay=swap
parameter is a best practice for performance.
Add link to Hugo template
Locate the base HTML file for your Hugo site. This is typically found in:
layouts/partials/head.html
layouts/_default/baseof.html
In Gokarna theme,
baseof.html
loads thehead.html
partial, so I will usehead.html
.Paste the Google Fonts link tags to
head.html
. The Google Fonts link must come before the link to your main.css file. For example:<head> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet"> <link rel="stylesheet" href="{{ 'css/main.css' | relURL }}"> </head>
If your
main.css
is loaded before the Google Fonts link, your browser won’t know what “Noto Sans JP” is yet, and it will fall back to the generic serif font.When you include the link in your HTML, Google Fonts automatically loads a stylesheet that defines the “Noto Sans JP” font-family for the browser. Specifically, it generates and serves the appropriate CSS with all the necessary
@font-face
declarations, including:- Proper
font-family
name - All requested weights/styles
- Multiple font formats (woff2, woff, etc.)
font-display: swap
- Cross-browser compatibility
You can verify this by visiting the Google Fonts URL directly in your browser. You’ll see the generated CSS with
@font-face
rules.- Proper
Use font in CSS
Finally, you need to instruct your blog’s CSS to use the font.
Determine where your primary CSS files are located (e.g., in your theme’s
static/css/
orassets/css/
directory). To override it safely, copy the file from the theme’s directory to your site’s rootassets/css/
directory.Edit the relevant CSS file to apply the new font to your desired elements (e.g., body, h1, p, etc.) using the
font-family
property. The font name must match what Google Fonts provided.On Google Fonts Embed code page, refer to the syntax in Noto Sans JP: CSS class for a variable style box. Use it as indicated in your css file. For example
.intro_text { font-family: "Noto Sans JP", sans-serif; font-weight: 700; font-style: normal; }
When the browser serves this element, it will
- Download the Google Fonts CSS (which contains
@font-face
) - Load the actual font files (indicated by
@font-face
) from Google’s servers - Apply your specified
font-family
,font-weight
, andfont-style
- Download the Google Fonts CSS (which contains
Save your changes and run
hugo server
to view the updated fonts. If it doesn’t work, see troubleshooting section .
As you can see, this is a very slick process. You just need to tweak the <link>
tag in your HTML and the font-family
in your CSS to load a font served via CDN.
Use self-hosted fonts
Unlike using fonts via CDN, you need to manually create the correct @font-face
declaration in main.css
or font.css
when you
- Self-host fonts (you downloaded font files to your server)
- Use custom fonts not available on Google Fonts or other CDN providers
- Need offline support or want to avoid external dependencies
- Have specific performance requirements (preloading, subsetting, etc.)
Here’s the complete workflow:
- Download font files (preferably WOFF2 + WOFF)
- Upload to blog’s
fonts/
directory - Use Transfonter to verify font metadata
- Create
@font-face
declarations matching the metadata - Test in browser DevTools → Inspector/Elements -> Computed panel
Determine @font-face
values
Get font metadata from the downloaded files with Transfonter . Upload the file and it generates ready-to-use
@font-face
code.Identify Key Properties
Property | Where to Find It | Example |
---|---|---|
font-family | Font name in metadata | "Noto Sans JP" |
font-weight | Weight value (100-900) | 700 |
font-style | Usually normal or italic | normal |
font-stretch | If applicable (rare) | normal |
Add
@font-face
to CSSAdd this block for each font weight and style to
font.css
@font-face { font-family: 'Your Font Name'; font-style: normal; /* or italic */ font-weight: 400; /* or 100, 200, 300, etc. */ font-display: swap; src: url('./fonts/your-font-file.woff2') format('woff2'), url('./fonts/your-font-file.woff') format('woff'); }
For example, if you downloaded Fraunces Regular (weight 400):
@font-face { font-family: 'Fraunces'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/Fraunces-Regular.woff2') format('woff2'), url('./fonts/Fraunces-Regular.woff') format('woff'); }
For Fraunces Bold (weight 700):
@font-face { font-family: 'Fraunces'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/Fraunces-Bold.woff2') format('woff2'), url('./fonts/Fraunces-Bold.woff') format('woff'); }
For Variable fonts
A variable font is a single font file that contains multiple styles and weights of a typeface. This means that instead of having separate files for each weight or style, all variations can be accessed through a single file, allowing for a continuous range of design variations.
Here’s an example of a variable font declaration:
@font-face { font-family: 'Fraunces'; font-style: normal; font-weight: 100 900; /* Range of supported weights */ font-display: swap; src: url('./fonts/Fraunces-VariableFont_opsz,wght.woff2') format('woff2-variations'); }
Best practices
Include multiple formats (for older browsers)
src: url('./fonts/font.woff2') format('woff2'),
url('./fonts/font.woff') format('woff'),
url('./fonts/font.ttf') format('truetype');
Use local()
to avoid re-downloading
src: local('Fraunces Regular'),
url('./fonts/Fraunces-Regular.woff2') format('woff2');
Organize by weight/style
Create separate @font-face
rules for each weight/style combination you need.
Troubleshooting
Sometimes after we’ve setup CDN link or local font and specified an HTML element to use the font, the browser doesn’t render it as expected. This is usually a timing issue where your main.css
is being processed before the Google Font’s stylesheet has fully loaded and defined the font on the page.
Here is the step-by-step troubleshooting guide:
Verify CSS loading order
In your Hugo
header.html
(or wherever your links are), the Google Fonts link must come before the link to yourmain.css
file. If yourmain.css
is loaded before the Google Fonts link, your browser won’t know what “Noto Sans JP” (or whatever the font is) is yet, and it will fall back to the generic serif.Check for theme overrides
If you are using a theme, another stylesheet from the theme might be loaded after your
main.css
and is overriding yourfont-family
declaration with a different one.To verify, use your browser’s Developer Tools (F12 or right-click → Inspect, then go to the Elements tab and click on the the HTML element to inspect).
- Inspect the HTML element.
- Look at the Styles panel and see which CSS rules are being applied to the
font-family
. - If you see your CSS with the right font, but it has a line struck through it, it means a more specific or later-loaded rule is overriding it.
Solution: Increase the specificity of your CSS rule, or ensure your CSS is the very last one loaded. You can try adding an ID to a parent element or using
!important
(e.g.,font-family: "Noto Sans JP", serif !important;
).Network request
In your browser’s Developer Tools, check the Network tab. Refresh the page and look for the request to
https://fonts.googleapis.com/css2?...
and the subsequent font file downloads (.woff2). They should all return an HTTP status code of 200. If you do not see them, or instead see a 404 or 403 error, the font isn’t being loaded.Verify if there is any rule (e.g.,
if
clauses) in your HTML that turns off loading the fonts.
Comments