This is lesson 7 in a tutorial on how to create a blog using NextJS, Sanity, and Vercel.
If you are just here for help with Styled Components then you will need to implement your own text for the elements or follow my guide from lesson 1.
- I also have a link to a working sandbox of this code in my Preview section that includes text data you can use instead.
- I didn't implement responsiveness for small screens. I set up the entire post information in a grid so implementing it won't be difficult. I'm leaving it up to you to create your own unique mobile view!
I've done some tweaking to the props we fetch. I will explain that part after the Planning, so if you just want Styled Components, you can skip ahead.
You should also create additional posts (at least have 2 in total) to see ourRecent Posts
sidebar.
Planning
It is never a good idea to style from your code. What I mean is, you should have an idea ahead of time on how you want things to look (even sketch it out), then replicate that with your code.
On the topic of 'sketching it out', I did just that. In only a couple of minutes I was able to create this Figma design.
You might need to signup to view it if you don't have a Figma account, but it's worth it to have one. The amount of time you save by sketching things out cannot be understated.
I didn't make the most complex UI ever, but it's perfectly fine for our needs. You can also click each element and see it's name (purpose) on the left hand side of the screen under Layers > Desktop-1
.
One of the elements is called Social Media Buttons
, so we are going to go ahead and install an npm package that is standard for almost every project.
In the command line of your project, run
npm i react-icons --save &&
npm i react-portable-text
Reading the React Icons docs will explain how to use it and let you search for any icon you want. All we have to do is find the code for the icon we want, import it, and insert it like a React component.
React Portable Text is what we are going to use to display the body
value we couldn't use in the last lesson.
Table of Contents
I don't know of the best way to show all of my code. I don't want to host it incase it changes later, so unfortunately the best way I can think of is to just slap it here in blocks.
But I am going to break it down into 5 parts.
-
Imports
- The couple of imports we need for our page
-
Styled Components
- Our Styled Components (done in file, not imported)
-
HTML Structure
- Our HTML layout with content
-
Props
- Changes to our getStaticProps
-
Preview
- Online sandbox so you can see how it should look or copy the
/pages/posts/[slug].js
file.
- Online sandbox so you can see how it should look or copy the
Imports
This can look different for you if you choose to use additional icons, I'm just importing 3 basic ones for now.
import PortableText from "react-portable-text";
import styled from "styled-components";
import Link from "next/link";
import { BsFacebook, BsTwitter, BsInstagram } from "react-icons/bs";
We import..
-
PortableText
to display thebody
of our post. -
styled
so we can create styled components. -
Link
to navigate to the homepage or to other posts. - The last line is importing a Facebook, Twitter, and Instagram icon from our
react-icons
.
Styled Components
FYI: I only spent a little over an hour on the styling. It isn't perfect and some of the code is redundant, but I'm already behind on this project so I just wanted to get something that looked nice in browser.
Before we get to the components, I added a couple things to our /styles/globals.css
file.
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
* {
padding: 0;
margin: 0;
font-family: 'Poppins';
color: #333;
}
body {
background: rgb(248, 248, 248)
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
- Poppins is now our default font for everything.
-
#333
is the default font color for everything. - The
body
now has a background color ofrgb(248, 248, 248)
.- This is an off white color that is easier on the eyes.
Below your imports but above your Post
component is where we will create our styling. Normally this is done in separate files, but since we aren't styling that many pages I don't see a reason to break it up.
I explained Styled Components very briefly at the start of these guides, but I will go over 1 I created for this page as a refresher.
Do not copy this into your file yet, it will be included in the full list of components I will post after this explanation.
const BackButton = styled.div`
border: 1px solid #333;
border-radius: 7px;
text-align: center;
font-size: 1.2rem;
width: fit-content;
padding: 4px 6px;
transition: all 0.3s;
:hover {
background: #333;
color: white;
cursor: pointer;
}
span {
position: relative;
bottom: 3px;
}
`;
-
First we create a component with the name of
BackButton
- This is a React component so it must be Pascal case (each word capital).
-
We declare that this component is a
styled.div
-
styled.
is from our import -
div
is letting React know what kind of HTML element we are assigning. in this case it will be a<div></div>
- All you have to do to use it is replace
<div></div>
in your code for<BackButton></BackButton>
and it will now be adiv
element with styling.
-
You also need to be aware that we are wrapping the CSS for our component in backticks.
-
Now inside is our basic CSS. I'm not going to go over the details of my CSS, or how I'm using
grid
for positioning because it's better for you to play around with it and find out yourself. But if you still do not understand the purpose of a line, you can either Google it, play around with it in your Browser DevTools, or even shoot me a message on Twitter and I can help explain further.- We apply styling to our
div
element - What happens when the
div
ishovered
- And finally we apply styling to any
span
elements inside of this div.
- We apply styling to our
After that quick example, here is the entire block of code for all of our styling. Remember, paste this below your imports and above your Post
component.
const PageContainer = styled.div`
padding: 70px 110px;
max-width: 1600px;
margin-inline: auto;
`;
const BackButton = styled.div`
border: 1px solid #333;
border-radius: 7px;
text-align: center;
font-size: 1.2rem;
width: fit-content;
padding: 4px 6px;
transition: all 0.3s;
:hover {
background: #333;
color: white;
cursor: pointer;
}
span {
position: relative;
bottom: 3px;
}
`;
const ContentContainer = styled.div`
display: grid;
grid-template-columns: 0.5fr 2fr 1fr;
grid-template-rows: 1fr auto;
grid-template-areas:
"header1 header header2"
"socials main links";
`;
const Header = styled.header`
grid-area: header;
text-align: center;
margin-bottom: 40px;
h1 {
text-transform: uppercase;
color: #222222;
font-size: 3rem;
}
span {
color: #464646;
}
`;
const SocialsContainer = styled.ul`
grid-area: socials;
display: flex;
flex-direction: column;
align-items: end;
list-style-type: none;
gap: 30px;
padding-right: 50px;
padding-top: 14px;
li {
cursor: pointer;
}
`;
const StyledLi = styled.li`
svg {
transition: transform 0.4s;
path {
color: #4040ac;
}
}
svg:hover {
transform: scale(1.2);
}
`;
const Body = styled.main`
grid-area: main;
h1 {
text-align: center;
font-size: 2.3rem;
margin-bottom: 15px;
padding-right: 160px;
}
h2 {
margin: 5px 0px;
}
div {
display: flex;
flex-direction: column;
gap: 5px;
}
`;
const RecentPostsContainer = styled.div`
padding-left: 50px;
grid-area: links;
display: flex;
flex-direction: column;
gap: 20px;
`;
const RecentPost = styled.div`
display: flex;
flex-direction: column;
border: 1px solid #333;
align-items: center;
transition: all 0.4s;
p {
font-size: 1.1rem;
font-weight: 500;
}
span,
p {
transition: all 0.4s;
}
:hover {
background: #333;
cursor: pointer;
span,
p {
color: white;
}
}
`;
I'm styling the
post.body
content from within theBody
component.
As you can see I only targetedh1
,h2
, anddiv
because those are what I have inside of my post's body. If you have other elements you can style them how you see fit, by targeting the type of the element and applying your style.
HTML Structure
Pasting all of that in won't do a darn thing until you add these components to your code.
Remember, your code will break because I changed how we import our posts. That will be explained next.
export default function Post({ post, posts }) {
const postsElements = posts.map((post, index) => (
<Link key={index} href={`/posts/${post.slug}`}>
<RecentPost>
<p>{post.title}</p>
<span>{new Date(post.publishedAt).toDateString().slice(4)}</span>
</RecentPost>
</Link>
));
return (
<PageContainer>
<Link href={`/`}>
<BackButton>
<span>๐</span>
<a>Home</a>
</BackButton>
</Link>
<ContentContainer>
<Header>
<span>{new Date(post.publishedAt).toDateString()}</span>
<h1>{post.title}</h1>
</Header>
<SocialsContainer>
<StyledLi>
<a href="https://facebook.com">
<BsFacebook size={"3rem"} />
</a>
</StyledLi>
<StyledLi>
<a href="https://twitter.com">
<BsTwitter size={"3rem"} />
</a>
</StyledLi>
<StyledLi>
<a href="https://instagram.com">
<BsInstagram size={"3rem"} />
</a>
</StyledLi>
</SocialsContainer>
<Body>
<PortableText content={post.body} />
</Body>
<RecentPostsContainer>
{postsElements}
</RecentPostsContainer>
</ContentContainer>
</PageContainer>
);
}
This is pretty simple, we are using our Styled Components we just created instead of normal HTML elements.
I will point out the couple of things that are important to take note of here.
export default function Post({ post, posts })
- You can see we are now importing
post
ANDposts
. You will see why in a second.
const postsElements = posts.map((post, index) => (
<Link key={index} href={`/posts/${post.slug}`}>
<RecentPost>
<p>{post.title}</p>
<span>{new Date(post.publishedAt).toDateString().slice(4)}</span>
</RecentPost>
</Link>
));
- We are mapping over the posts we fetch to create a list of links to every post (excluding the post we are currently viewing).
<BsFacebook size={"3rem"} />
- React Icons are styled by this
size
attribute so we have these on each one to increase them to3rem
.
<Body>
<PortableText content={post.body} />
</Body>
- You can see our
PortableText
we imported being used to display thebody
of our post by adding it as thecontent
. - It's annoying to style
PortableText
, so I just surround it in a styled div and apply the styling there.
<RecentPostsContainer>{postsElements}</RecentPostsContainer>
- And finally our
postsElements
variable we created being surrounded by a styled component.
Props
I came up with the idea to display all of our blog posts in the right sidebar. Before we were only fetching our current post, but now we need to fetch all of them as well.
export async function getStaticProps({ params }) {
const query = `*[_type == "post"] {
_id,
title,
publishedAt,
'slug': slug.current,
body
}`;
let posts = await client.fetch(query);
const post = await posts.find((post) => post.slug == params.slug);
posts.splice(
posts.indexOf(posts.find((post) => post.slug == params.slug)),
1
);
return {
props: { posts, post },
};
}
Not a lot changed, but I'll explain the general idea of it.
We no longer fetch just the current post, we now get every single entry with a type of
post
.After all of our posts are returned, we run a
find
method to match the post in our array the contains the sameslug
as our url params.Then we
splice
out the post we just fetched because we don't want to display that one in our list of posts on the side.Finally we return all of our
posts
excluding our current post, and our currentpost
.
Preview
CodeSandbox Code -- Fullscreen Preview
Here you can view my code, there are some changes to remove fetching and the file structure is obviously incorrect. This is supposed to just be a reference to how the page and /pages/posts/[slug].js
code should look for you (minus the slight changes).
That's the end of this part. We didn't alter our project too much, but we now have a great looking page to display each of our posts.
I know that was a lot of code, and there is always a possibility something goes wrong on your end. So if there is something you just cannot seem to fix, send me a message on Twitter and I will help you out as much as I can.
Next Lesson
I think I will skip styling the homepage for now. It would be a fantastic exercise to practice styled components.
At the end of the tutorial I'll have a link to this project which will include my personal styling if you are interested in that.
The next few lessons are going to be less code oriented and more involved with using Vercel, GitHub, and Sanity utilities.
Our goal is to...
Push our code to GitHub.
Deploy our website on Vercel.
Deploy our Sanity Studio interface to Vercel.
Top comments (0)