visit
First, we will define our navigation routes in the gatsby-config.ts
siteMetadata
section. This will help us later when we want to ensure our navigation links are uniform and have a single source of truth.
The
siteMetadata
section of your config is an excellent place to store any information about the site to which you will want easy access. Contact info, social media accounts, and navigation route definitions are perfect examples of things to store there.
Open up your gatsby-config.ts
file and add the following code to the siteMetadata
section:
siteMetadata: {
title: `My Gatsby Blog`,
siteUrl: `//www.yourdomain.tld`,
// ↓ Add this section
navigation: [
{
name: 'About',
path: '/about',
},
{
name: 'Blog',
path: '/blog'
}
]
},
useMetadata
hook.We will often refer to the site metadata, so it's a good idea to have a reusable utility to quickly gather the information. We are going to do that by making use of a custom React hook we'll call useMetadata
.
Create a new folder and file called src/hooks/use-site-metadata.ts
mkdir src/hooks
touch src/hooks/use-site-metadata.ts
// src/hooks/use-site-metadata.ts
import { graphql, useStaticQuery } from 'gatsby';
export const useSiteMetadata = () => {
const data = useStaticQuery<{
site: { siteMetadata: Queries.SiteSiteMetadata };
}>(graphql`
query SiteMetadataQuery {
site {
siteMetadata {
title
siteUrl
navigation {
name
path
}
}
}
}
`);
return data.site.siteMetadata;
};
This custom hook can be added to any page or component in our codebase, making accessing the site's information easy. Of course, as we add more metadata, we'll need to update this query.
You may prefer the file name
useSiteMetadata.ts
; I've settled into a specific naming convention and folder structure for all my TypeScript projects, and your mileage may vary. Feel free to modify this to whatever you prefer. No judgment! 😊
NOTE: You'll need to restart the development server for the site metadata to be recognized and for the TypeScript types to be generated correctly.
mkdir ./src/components/
touch ./src/components/PageHeader.tsx
// src/components/PageHeader.tsx
import { Link } from 'gatsby';
import { StaticImage } from 'gatsby-plugin-image';
import React from 'react';
import { useSiteMetadata } from '../../hooks/use-site-metadata';
export const PageHeader: React.FC = () => {
const { title, navigation } = useSiteMetadata();
return (
<header className="bg-white">
<div className="container mx-auto flex max-w-5xl items-center justify-between py-6">
{title && (
<Link
to="/"
className="flex items-center gap-2 px-4 sm:w-auto sm:pb-0 lg:px-0"
>
<StaticImage
src="../../images/icon.png"
alt={title}
layout="fixed"
height={35}
width={35}
/>
<span className="whitespace-nowrap text-xl font-bold uppercase">
{title}
</span>
</Link>
)}
<nav className="flex w-full items-center justify-between text-sm sm:w-auto">
<ul className="flex justify-between px-2 sm:px-0">
{navigation?.map(
(nav) =>
nav?.path && (
<li key={nav.path} className="px-2 sm:px-4">
<Link to={nav.path}>{nav.name}</Link>
</li>
)
)}
</ul>
</nav>
</div>
</header>
);
};
This defines the PageHeader
component we'll use for all our blog pages. In the next section, we will create a Footer
component for our pages.
touch ./src/components/PageFooter.tsx
// src/components/PageFooter.tsx
import { Link } from 'gatsby';
import { StaticImage } from 'gatsby-plugin-image';
import React from 'react';
import { useSiteMetadata } from '../../hooks/use-site-metadata';
export const PageFooter: React.FC = () => {
const { title, navigation } = useSiteMetadata();
return (
<footer className="bg-purple-200">
<div className="container mx-auto flex max-w-5xl flex-col justify-evenly py-12 sm:flex-row">
<div className="mb-12 sm:mb-0">
{title && (
<div className="mb-4 flex items-center justify-center gap-4 sm:-ml-6">
<StaticImage
className="inline-block"
layout="fixed"
height={20}
width={20}
src="../../images/icon.png"
alt={title}
/>
<span className="text-xl font-bold uppercase text-black">
{title}
</span>
</div>
)}
</div>
<div className="text-center sm:text-left">
<ul className="flex flex-col items-center gap-4 sm:flex-row">
{navigation?.map(
(nav) =>
nav?.path && (
<li key={nav.path}>
<Link className="underline" to={nav.path}>
{nav.name}
</Link>
</li>
)
)}
</ul>
</div>
</div>
</footer>
);
};
Notice that we pull the navigation from
siteMetadata
. This is an example of how having that site metadata readily available can lead to better consistency across our pages.
In the next section, we'll create a PageLayout
component and add the PageFooter
and PageHeader
.
touch ./src/components/PageLayout.tsx
// src/components/PageLayout.tsx
import React from 'react';
import { PageFooter } from '../page-footer';
import { PageHeader } from '../page-header';
export const PageLayout: React.FC<React.PropsWithChildren> = ({ children }) => {
return (
<main className="font-sans font-light">
<PageHeader />
<div className="mx-auto mb-12 max-w-5xl">{children}</div>
<PageFooter />
</main>
);
};
Notice that each page will be wrapped in a
<main>
tag, where we set the default font family and weight. You can add other defaults here as well.
To start, open up ./src/pages/index.tsx
in your editor and erase everything already there. We don't like it. Then add this:
// src/pages/index.tsx
import type { HeadFC, PageProps } from 'gatsby';
import * as React from 'react';
import { PageLayout } from '../components/page-layout';
const IndexPage: React.FC<PageProps> = () => {
return (
<PageLayout>
<div className="container mx-auto px-4 lg:px-0">
<h1 className="mb-8 text-4xl font-bold lg:text-5xl">My Gatsby Blog</h1>
<span className="text-lg">This is my Gatsby Blog home page!</span>
</div>
</PageLayout>
);
};
export default IndexPage;
export const Head: HeadFC = () => <title>Home Page</title>;
touch ./src/pages/about.tsx
TIP: Any file inside the pages directory automatically becomes a page with a route corresponding to the file name and folder structure above it.
import { graphql, HeadFC, PageProps } from 'gatsby';
import { GatsbyImage, getImage } from 'gatsby-plugin-image';
import * as React from 'react';
import { PageLayout } from '../components/page-layout';
const AboutPage: React.FC<PageProps<Queries.AboutPageQuery>> = ({ data }) => {
const profileImage = data.profileImage
? getImage(data.profileImage.childImageSharp)
: null;
return (
<PageLayout>
<div className="container mx-auto px-4 lg:px-0">
<h1 className="mb-8 text-4xl font-bold lg:text-5xl">About Us</h1>
<span className="text-lg">
Welcome to our blog's "About" page! We're glad you're here. This page
is all about us, the people behind the blog. We're passionate about
sharing our knowledge, opinions, and experiences with the world, and
we hope you find our content informative and enjoyable.
</span>
<div className="mt-8 flex flex-col gap-4 sm:flex-row sm:items-center">
{profileImage && (
<div className="w-full sm:w-1/5">
<GatsbyImage
image={profileImage}
alt="My Profile Image"
className="w-full"
/>
</div>
)}
<div className="flex-1 text-lg">
Meet John, the tech enthusiast behind our Javascript blog. With over
a decade of experience in web development, John has become a
seasoned expert in all things Javascript. His passion for coding and
his love of sharing knowledge have led him to create a blog that
aims to help people master the art of Javascript and create amazing
web experiences.
</div>
</div>
</div>
</PageLayout>
);
};
export default AboutPage;
// This query will get run when the page is built, and the data will be passed in
// as the `data` property to the exported `AboutPage` above.
export const pageQuery = graphql`
query AboutPage {
profileImage: file(relativePath: { eq: "profile.jpg" }) {
childImageSharp {
gatsbyImageData(layout: FULL_WIDTH)
}
}
}
`;
export const Head: HeadFC = () => <title>About Page</title>;
NOTE: Notice that the "About" page uses the
PageLayout
component just like the "Home" page. This gives each page a consistent look and feel without having to duplicate code.
Also published here.