This is the third part of the "Building Personal Blog Website" series. In the series we'll setup a free CMS to hold our blog content on Railway, create a React app with Next.js' static site generation and TailwindCSS to present the articles and host it on Netlify.
All articles: Part 1: Hosting free Strapi CMS on Railway Part 2: Setting up a basic Next.js app locally Part 3: Basic Next.js app - blog post page
You can see all your posts already on your homepage as cards. But now let’s go a step further - create a subpage for every post.
Before you create a subpage for a specific blog post - let’s just do a small adjustment in your current app. You’ll make changes in the _app.js
file. Markup in this file is preserved between pages and works as a kind of a page layout. By default it renders just a page component. Now you’ll move the header from your homepage there. Use this code in _app.js
:
function MyApp({ Component, pageProps }) {
return (
<div className="flex flex-col items-center">
<h1 className="text-3xl uppercase font-serif font-bold my-8">
My personal blog
</h1>
<Component {...pageProps} />
</div>
);
}
And remove the same markup from your index.js
file:
export default function Home({ posts }) {
return (
<section className="grid grid-cols-3 gap-4">
{posts.map((post) => (
<BlogPostPreview post={post} key={post.attributes.slug} />
))}
</section>
);
}
Now whenever you’ll create a new subpage - it’ll always have the header “My personal blog”. Of course it would be best to add a navigation bar and footer here, but that’s not the main focus in this article series, so we’ll go back to this in the future, when there will be something to navigate to.
The next step would be creating a page for every blog post. Of course you’d want it to be generic so with every new post you’d get a new /post/slug-of-blogpost
page. You can easily achieve that in Next.js using dynamic routes. Dynamic routes allow you to create a template page and then during site generation all the needed information are figured out by Next.js. Let’s just see how this works.
In pages
directory create a new folder post
. This will correspond to a new route /post
. Inside create a file [slug].js
- square brackets are important - they indicate that this is a dynamic route. In our case it will allow us to get a specific route for each blog post (for example /post/test-post
or /post/my-first-post
).
As you are creating a statically generated site you need to tell Next.js which routes it should generate. One way to provide this information is by creating a getStaticPaths
function in your [slug].js
file. This function will get slugs of all the blog posts and based on that it will inform Next.js which routes are needed.
export async function getStaticPaths() {
const { data } = await client.query({
query: gql`
query Posts {
posts(
filters: { publishedAt: { notNull: true } }
) {
data {
attributes {
slug
}
}
}
}
`,
});
return {
paths: data.posts.data.map((item) => ({
params: { slug: item.attributes.slug },
})),
fallback: false,
};
}
The filters
in the query ensure that you will get only published posts.
Now you have the list of slugs and it’s time to use it to fetch every post data. Let’s create a getStaticProps
method in the same file.
export async function getStaticProps({ params }) {
const { data } = await client.query({
query: gql`
query Posts {
posts(
sort: "publishedAt:desc"
pagination: { limit: 1 }
filters: { slug: { eq: "${params.slug}" } }
) {
data {
attributes {
title
slug
content
cover {
data {
attributes {
url
}
}
}
author {
data {
attributes {
username
}
}
}
tags {
data {
attributes {
tagId
name
}
}
}
}
}
}
}
`,
});
return {
props: {
postData: data.posts.data[0],
},
};
}
You’ll get all the data needed to properly display the post on your page. You query the GraphQL for a post
with a specific slug
. Unfortunately Strapi API does not allow you to get one specific post using the slug. It’s only possible using post ID
, but you want to have more descriptive links than for example /post/2
. /post/a-blog-post
looks definitely better. That’s why you’ll make a standard Posts
query with a filter to get the one with a specific slug. And the returned data is put into props
of BlogPost
component which you’ll create next.
Now the time has come to create your blog post page. Still in [slug].js add a new component (place it over the getStaticPaths and getStaticProps methods).
export default function Post({ postData }) {
return <div>{postData.attributes.content}</div>;
}
But wait! Your content is written in Markdown - so the end result would look like this:
That’s not exactly what we want! To help you display Markdown properly I suggest using two additional libraries - react-markdown
and TailwindCSS Typography
. First one help format markdown properly, second one is a plugin for TailwindCSS which provides basic styling for articles. Let’s install both:
yarn add react-markdown @tailwindcss/typography
Now update your tailwind.config.js
so it’s aware of the newly installed plugin:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [require("@tailwindcss/typography")],
};
With this out of the way you’ll be able display your blog posts properly:
export default function Post({ postData }) {
return (
<ReactMarkdown className="prose lg:prose-xl xl:prose-2xl prose-slate">
{postData.attributes.content}
</ReactMarkdown>
);
}
You have one more thing to do before you’ll be able to see your blog posts in action. You need to hook Blog Post Page to the Blog Post Preview - so when the user clicks on the preview he/she will go to the full post.
Use next/link
which is better suited for routing in Next.js apps. Go to the BlogPostPreview.jsx
and replace existing <a href=”#”>
and its content with:
<Link href={`/post/${post.attributes.slug}`}>
<a>
<Image
className="w-full"
src={post.attributes.cover.data.attributes.url}
alt=""
width={1000}
height={480}
objectFit="cover"
/>
<div className="px-6 py-4">
<div className="font-bold text-xl mb-2">
{post.attributes.title}
</div>
<p className="text-gray-700 text-base">{post.attributes.excerpt}</p>
</div>
</a>
</Link>
Now rebuild your app and then start the development mode.
yarn build
yarn dev
Open http://localhost:3000 and click on any of the previews - you should be redirected to the blog post page:
That’s better! Of course feel free to experiment with the styling of the whole page - I’m not focusing on this here as this is not a course on css and layouts.
And that’s it! In the next part of this guide you’ll finally deploy the page on Netlify, so stay tuned!