Chris.luChris.lu header image, displaying an 80s style landscape and sunset

Styling the navigation and using next/font

Now that our Navigation is working, we will add a few touches of color and use next/font to add a custom web font to our project

When using next/font, you can use any font available on google fonts or use your own local font(s), the best part about using next/font is that it will optimize the loading of the font and make sure that there are zero layout shifts, also when using next/font we are self-hosting the font(s), which means we will NOT need to make an external network request to fetch the font, which will decrease loading times but also increase privacy as NO request is made to google servers

There is one optimization you can see when you deploy on vercel, Next.js will add a link element, with the rel attribute set to preload, to your pages <head> element. This <link> will tell the browser to preload your font (which will decrease the loading time when getting the font):

<link rel="preload" href="/_next/static/media/00000-s.p.woff2" as="font" crossorigin="" type="font/woff2">
Warning

When I run the website in production mode locally (to run a production build locally you can use commands npm run build and then npm run start), I can NOT see the preload optimization (using Next.js 14.2.11). I'm not sure this is a bug, but I found the Next.js Issue #62332 which seems to be related. I also tried Next.js 15 and there the problem seems to be fixed, I can even see the <link> element that preloads the font(s), when I run the project in development

next/font with an open source font

Because we are using the font for our navigation, let's start by editing our layout.tsx file (that is in the app folder) and import the font from there:

/app/layout.tsx
import './global.css'
import { Metadata } from 'next'
import HeaderNavigation from '@/components/header/Navigation'
import { Kablammo } from 'next/font/google'
 
export const metadata: Metadata = {
    title: 'Next.js',
    description: 'Generated by Next.js',
}
 
const kablammo = Kablammo({
    subsets: ['latin'],
    variable: '--font-kablammo',
    weight: ['400'],
    display: 'swap',
})
 
export default function RootLayout({
    children,
}: {
    children: React.ReactNode
}) {
    return (
        <html lang="en" className={kablammo.variable}>
            <body>
                <header>
                    <HeaderNavigation />
                </header>
                <main>{children}</main>
                <footer>
                    <p>My Footer</p>
                </footer>
            </body>
        </html>
    )
}

Line 4: we import the font called Kablammo from next/font, you can find Kablammo on google fonts

Lines 11 to 16: we set some options for the font:

Tip

Because the fonts are fully typed, you can just type an option name and will get a list of values that you can choose from

Line 24: we add the font style to our HTML element

Warning

In the last chapter of the Next.js "font optimization" documentation, there is a very import information about how Next.js caches fonts at build time, quote:

Every time you call the localFont or Google font function, that font is hosted as one instance in your application. Therefore, if you load the same font function in multiple files, multiple instances of the same font are hosted.

So if you have a font that you need in multiple pages it is probably best to load it in the layout and not load the same font in several different pages; have a look at the Next.js "font optimization" documentation where they list several options you can use to avoid having multiple instances of the same font file in your Next.js cache; this is important because if you have multiple instances of the font it would mean that the browser will load the font multiple times, instead of just once

next/font with a local font (optional)

Note

I'm not going to deep dive into this, but just know that if you prefer using a local font that you downloaded in the past, you can do that too using next/font

Here is a simple example of how to add a local font to our layout.tsx file:

/app/layout.tsx
import './global.css'
import { Metadata } from 'next'
import HeaderNavigation from '@/components/header/Navigation'
import localFont from 'next/font/local'
 
export const metadata: Metadata = {
    title: 'Next.js',
    description: 'Generated by Next.js',
}
 
const myFont = localFont({
    src: './assets/fonts/myFont.woff',
    variable: '--font-myFont',
    weight: ['400'],
    display: 'swap',
})
 
export default function RootLayout({
    children,
}: {
    children: React.ReactNode
}) {
    return (
        <html lang="en" className={myFont.variable}>
            <body>
                <header>
                    <HeaderNavigation />
                </header>
                <main>{children}</main>
                <footer>
                    <p>My Footer</p>
                </footer>
            </body>
        </html>
    )
}

Line 4: instead of importing a google font from next/font we are this time importing localFont function from next/font

Lines 11 to 16: we replace the previous code, with new code that uses the localFont function, the options we use are similar to those we used previously, the only difference is that we replace the subsets with an src option, as src you should set a value that is the path to the local font, assuming it is located inside of your public folder, so as our font has an src set to ./assets/fonts/myFont.woff tells next/font that we have stored our local myFont.woff font file in /public/assets/fonts

Line 24: same as before, we add the font style to our HTML element

Next, we are going to add a stylesheet for our Navigation component and use the new font for the navigation items

Create a new navigation.module.css file in the /components/header folder and insert the following content:

/components/header/navigation.module.css
.navigation {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 60px;
    background-color: hsl(var(--background-dark-value) / 0.9);
    border-bottom: 1px solid hsl(var(--text-dark-value) / 0.5);
    position: fixed;
    top: 0;
}
 
.link {
    padding: 0 var(--spacing);
    font-size: xx-large;
    text-decoration: none;
    font-family: var(--font-kablammo);
}
 
.active {
    font-weight: bold;
}

Line 17: we use the --font-kablammo variable we defined when setting the options for the font in the layout.tsx file

The final step is to edit our Navigation.tsx file in the /components/header folder and start using the styles we just created, like so:

/components/header/Navigation.tsx
'use client'
 
import { usePathname } from 'next/navigation'
import type { Route } from 'next'
import Link from 'next/link'
import styles from './navigation.module.css'
 
interface IMainMenuItem {
    pathname: string
    text: string
}
 
const HeaderNavigation: React.FC = () => {
 
    const currentPagePathname = usePathname()
 
    const mainMenuItems: IMainMenuItem[] = [
        { pathname: '/', text: 'Home' },
        { pathname: '/blog', text: 'Blog' },
    ]
 
    const isActiveCheck = (menuItemPathname: string) => {
 
        let isActiveClass = ''
 
        if (menuItemPathname.length > 1) {
            if (currentPagePathname.startsWith(menuItemPathname)) {
                isActiveClass = styles.active
            }
        } else {
            // exception for homepage path which is "/"
            // in that case the page path needs to be equal
            if (currentPagePathname === menuItemPathname) {
                isActiveClass = styles.active
            }
        }
 
        return isActiveClass
    }
 
    return (
        <>
            <nav id="navigation" className={styles.navigation}>
                {mainMenuItems.map((menuItem) => {
                    return (
                        <Link
                            href={menuItem.pathname as Route}
                            key={menuItem.pathname}
                            className={`${isActiveCheck(menuItem.pathname)} ${styles.link}`}
                            title={menuItem.text}
                        >
                            {menuItem.text}
                        </Link>
                    )
                })}
            </nav>
        </>
    )
}
 
export default HeaderNavigation

Line 6: we import the new CSS Module containing the Navigation component stylesheet

Lines 28 / 34 / 43 and 49: we use the 3 CSS rules, the .active rule will be used to make the Navigation entry that matches the current pathname bold, and the .navigation rule uses position fixed so that the navigation bar will always stay on top and finally the .link rule adds the new Kablammo font all the links of the navigation

There is one more change we need to do, because we made the navigation to have a fixed position, it is now as if it was not part of the document anymore, meaning it will NOT take up space anymore, as it will be floating above the content. This will cause the page content to move up and eventually be under the navigation

This is why we will edit our global.css file and edit the body class:

/app/global.css
body {
    /* remove any margin that browsers add to body */
    margin: 0;
    /* compensate for the fixed navigation height */
    padding-top: 60px;
}

Lines 36 to 37: we add a padding-top property and set its value to the same size as the height of the navigation, this will ensure that when the page has loaded, there is no content displayed under the navigation because the content of the body will have been pushed down a bit (later when scrolling the content will move under the navigation)

Now launch the dev server if it isn't already running (using the npm run dev command), then open your browser and go to the project's homepage at http://localhost:3000/ to see the styled navigation which is using our custom font instead of the default browser font, for yourself

Congratulations 🎉 you now have a much better-looking navigation component and learned how to use next/font

If you liked this post, please consider making a donation ❤️ as it will help me create more content and keep it free for everyone