Next.js / Ghost Tutorial: 5 - Routing and Layout Components
In the last blog post 4 - Styling with Vanilla CSS and Tailwind we saw how to style our application with css.
This time we will talk about routes and create the layout components required to display blog posts. This is the last step before we start pulling and rendering data from Ghost CMS.
Building blocks
Our page layout will be made out the following four components:
-
Layout - The outer most component. Acts as a container for the other components
-
Header - The area at the top of the page, with the navigation bar and a logo
-
Footer - The area at the bottom of the page with links to static pages and social media handles
-
PostPreviewCard - A compact preview of a single blog with title, image, publish date and associated tags
Pages and routes
Next.js comes with a smart file-based routing logic: for each file located in the /pages/ folder Next.js will "automagically" create a same-named route. You don't need any third-party routing libraries at all.
Here is a small example:
/pages/animals/fish.js creates the route: https://yourhostname/animals/fish
As seen in the example above, the subfolder animals inside /pages/ corresponds to a nested route. This can be used to create distinct route paths for your website:
http(s)://yourhostname /blog/...
http(s)://yourhostname /shop/...
http(s)://yourhostname /members/...
For instance, the homepage route (/) is associated with the file /pages/index.js without our doing.
Dynamic routes
Files named [xyz].js - notice the brackets - have a special meaning in Next.js as they are used to create dynamic routes.
In contrast to static routes, which are created based on fixed pages already known at build time, dynamic routes are constructed based on data fetched at runtime (however this is not totally true with static generated sites (SSG) since routes are also resolved at generation/build time).
Later we will learn the powerful combination of the Next.js functions getStaticPaths() and getStaticProps() and demonstrate their convenience with a practical example.
Project skeleton
Using your favorite file manager or code IDE to create a folder structure as follows (pages and styles should already be in place from the previous tutorial episodes):
Tip: If you are using an operating system with a unix-like shell, you can execute the following commands on the top level of your project folder:
mkdir -p api
mkdir -p components
mkdir -p pages/blogpages
mkdir -p pages/posts
mkdir -p pages/tags
mkdir -p public
Header component (2)
The header component consists of a navigation bar with a profile picture a the top and some links below.
The easiest way to serve static assets like images, favicons, etc. is storing them in the public folder.
Choose a profile picture, name it profilepic.jpg and store it the public folder. 128x128px is probably a good size. To check if it is accessible, make sure that your development server is running (npm run dev) and point your browser to the URL http://localhost:3000/profilepic.jpg.
Next, create the file header.js inside the /components folder and insert the following code:
// File: /components/header.js
import Link from 'next/link'
export default function Header({ home }) {
return (
<nav className="bg-gray-800 p-2 fixed z-10 top-0 w-full">
<div className="container mx-auto flex flex-col flex-wrap items-center">
<div>
{home ? (
<img
className="rounded-full w-32 border-white border-4 border-solid"
src="/profilepic.jpg"
alt="My pic"
/>
) : (
<Link href="/Home" as={'/'}>
<a>
<img
className="rounded-full w-32 border-white border-4 border-solid"
src="/profilepic.jpg"
alt="My pic"
/>
</a>
</Link>
)}
</div>
<div>
<h1 className="font-sans text-3xl font-semibold text-center text-white my-2">
My Blog
</h1>
<div className="flex mb-2">
<div>
<Link href="/tags/tagoverview" as="/tags/tagoverview">
<a className="text-white px-8 truncate">Tags</a>
</Link>
</div>
<div>
<Link href="/blogpages/[slug]" as="/blogpages/about">
<a className="text-white px-8 truncate">About me</a>
</Link>
</div>
<div>
<Link href="/blogpages/[slug]" as="/blogpages/legal">
<a className="text-white px-8 truncate">Legal</a>
</Link>
</div>
</div>
</div>
</div>
</nav>
)
}
Tip:Instead of copying and pasting the code, type it in. This helps you understanding the code better and is more easily absorbed into long term memory
What is happening here? First, we import the Next.js built-in component next/link which is used to (surprise) link to other pages (more information under Further readings at the bottom of this article).
We then declare the component function Header which takes the boolean parameter "home". home is only true when the component is called from index.js and is used with a ternary operator to set/unset a link back to the homepage:
home ? (only the picture) : (picture and link to homepage)
Finally, we create the links to the blogpages.
As you can see, we are using Tailwind CSS classes to style the component, for example the <a> tag:
<a className="text-white px-8 truncate">Legal</a>
Footer component (2)
The footer component is very similar to the header except that we are mixing next/link and regular <a> tags to create links to pages outside of the blog site.
Get some nice social network icons and place them in the public folder beforehand. I've named mine instagram.png and twitter.png.
Create the file footer.js under /components and fill it with the following code:
// File: /components/footer.js
import Link from 'next/link'
export default function Footer() {
return (
<footer className="bg-gray-800 w-full">
<div className="flex w-full py-4">
<div className="flex w-1/2 px-5">
<Link href="/blogpages/[slug]" as="/blogpages/copyright">
<a className="text-white">Copyright</a>
</Link>
</div>
<div className="flex w-1/2 justify-end">
<div className="px-5">
<a
href="https://twitter.com/_Filipe_Matos"
target="_blank"
rel="noopener noreferrer"
>
<img src="/twitter.png" alt="Twitter" className="text-white" />
</a>
</div>
<div className="px-5">
<a
href="https://www.instagram.com/fmaiamatos/"
target="_blank"
rel="noopener noreferrer"
>
<img
src="/instagram.png"
alt="Instagram"
className="text-white"
/>
</a>
</div>
</div>
</div>
</footer>
)
}
Done!
Layout component (1)
Think of the layout component as a container that holds the header, footer, and page content together (as seen in the wireframe image above).
First, create the file layout.js under /components
The code is pretty straight forward:
// File: /components/layout.js
import Header from './header'
import Footer from './footer'
import Head from 'next/head'
export default function Layout({ home, _title, children }) {
return (
<div className="top-40 absolute min-w-full">
<Head>
<title>{_title}</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Header home={home} />
<div>
<main>
<div id="content" className="container p-6 max-w-screen-lg mt-6 mx-auto">
{children}
</div>
</main>
</div>
<Footer />
</div>
)
}
In the first two lines, we import the newly created components Header and Footer.
The third line imports the Next.js built-in component next/head required to set the page title that we are getting passed as an argument. Here we also set the favicon (should be stored in the public folder first).
If you want to learn more about next/head, check the section "Further reading" below.
Tip: Title and meta tags are important for Search Engine Optimization (SEO)
You may also have noticed that we are forwarding the boolean home to the Header component.
The children object inside the <div id="content" ...> holds the payload for the page. This can be either a blog post or whatever we pass on.
Go ahead and commit the changes to the git repository:
git add .
git commit -m "Added Tailwind css"
git push
Done!
Filipe Matos