BloggingWeb DevelopmentNext.JSMDX

(OUTDATED) Welcome to my Blog! - The backstory, How it works, etc.

David's profile imageDavid Ilie

/ June 24, 2021

1,242 words7 min read

Post picture

WARNING

The entire arhitecture of the website has been changed to a more maintable stack and therefore the Blog aspect has been changed as well. Feel free to read the old design but if you want to read the new design instead, click here.

Hello There!

Hey! Welcome to my very fist blog post (on this website)! I used to run a blog like this one on a previous website (The David Ones) but I decided that it would be more fun to create the blog myself instead of using Ghost as the publisher, which is what I did.

Talking about how the blog is made, I decided to challenge myself to make this a fully fledged blog.

Quick note, this post will only cover one aspect of this website (which is about this blog). However, this website has many other aspects as well that I might cover later down the line.

What am I using?

I am a huge fan of Next.JS, so its already extremely easy to store the blog posts locally due to the fact that Next.JS uses Server Side Rendering as opposed to React's Client Side Rendering.

Server Side Fetching Example
1export const getServerSideProps: GetServerSideProps = async () => {
2 const response = await fetch(`https://davidilie.com/api/agenda/job/github`);
3
4 const repos = await response.json();
5 return { props: { repos, revalidate: 600 } };
6};

The code above fetches data for my projects page. However you will see later an example with my blog.

Along with this, I use MDX which is the simplest way to use custom React Components with my Markdown code. For instance, I use a custom component to display information "blocks" in a more good-looking way:

Custom MDX Component Example
1export const InfoQuote = ({ children }) => (
2 <div className="px-6 py-1 mb-5 rounded-lg border-l-4 leading-relaxed text-gray-300 relative border-blue-500 bg-blue-500 bg-opacity-10">
3 <div
4 className="text-center bg-gray-900 rounded-full w-10 h-10 flex items-center justify-center"
5 style={{
6 float: "left",
7 position: "absolute",
8 top: "-30px",
9 left: "-20px",
10 }}
11 >
12 <HiOutlineInformationCircle className="text-2xl text-blue-500" />
13 </div>
14 <div className="p-0 m-0 text-lg mb-3">{children}</div>
15 </div>
16);

How does it work?

"If it works, leave it alone." - Every programmer in 2021

Each blog post is a .mdx file which is kept at src/data/blog/*post*.mdx. When you visit the Blog page. A function is called which gets all my blog posts and returns them as props which can be displayed onto the page:

Helper function for MDX
1export async function getAllFilesFrontMatter() {
2 const files = fs.readdirSync(
3 path.join(process.cwd(), `src`, `data`, `blog`)
4 );
5
6 return files.reduce((allPosts, postSlug) => {
7 const source = fs.readFileSync(
8 path.join(process.cwd(), `src`, `data`, `blog`, postSlug),
9 `utf8`
10 );
11
12 const slug = postSlug.replace(`.mdx`, ``);
13
14 const { data } = matter(source);
15
16 return [
17 {
18 ...data,
19 slug: slug,
20 },
21 ...allPosts,
22 ];
23 }, []);
24}

However, that is just to get a list of blog posts. But how do we get the information in the blog post actually displayed?

Well, we will be using dynamic pages which will take our blog name and find the correct .mdx file.

Find correct post from slug
1export async function getStaticPaths() {
2 const posts = await getFiles(`blog`);
3
4 return {
5 paths: posts.map((p) => ({
6 params: {
7 slug: p.replace(/\.mdx/, ``),
8 },
9 })),
10 fallback: false,
11 };
12}
13
14export async function getStaticProps({ params }) {
15 const post = await getFileBySlug(`blog`, params.slug);
16
17 return { props: { ...post } };
18}

With the file found, we can now proceed to the more complicated stuff.

How is the informaton formatted?

A layout is used for each blog post to show the elements that needs to persist between each blog post. This consists on the things you see at the very top (title, author, etc).

Article Code
1<article className="text-black dark:text-white flex flex-col justify-start pt-28 w-full min-h-screen mx-auto max-w-3xl">
2 {frontMatter.tags && (
3 <div className="flex w-full px-3 mb-4 justify-start flex-wrap">
4 {frontMatter.tags.map((tag, i) => (
5 <BlogBadge tag={tag} key={i.toString()} />
6 ))}
7 </div>
8 )}
9 <h1 className="2xl:text-5xl xl:text-5xl md:text-5xl lg:text-5xl text-4xl font-semibold px-4">
10 {frontMatter.title}
11 </h1>
12 <div className="flex flex-wrap justify-between items-center max-w-3xl mx-auto mb-6 px-3 mt-5 w-full">
13 <div className="flex items-center">
14 <span className="inline-flex items-center justify-center py-2 text-xs font-bold leading-none rounded-md">
15 <Image
16 className="rounded-full"
17 src={frontMatter.by.avatar}
18 width="25px"
19 height="25px"
20 blurDataURL={shimmer(1920, 1080)}
21 alt={`${frontMatter.by}'s profile image`}
22 />
23 <span className="ml-1 header-gradient text-lg mr-1">
24 {frontMatter.by.name}
25 </span>
26 </span>
27 <h1 className="text-gray-800 dark:text-gray-300">
28 {" / "}
29 {format(parseISO(frontMatter.publishedAt), "MMMM dd, yyyy")}
30 </h1>
31 </div>
32 <h1>
33 {frontMatter.wordCount.toLocaleString() + " words"}
34 {``}
35 {frontMatter.readingTime?.text}
36 {``}
37 <BlogViewCounter slug={frontMatter.slug} />
38 </h1>
39 </div>
40 <div className="mx-auto">
41 <Image
42 alt="Post picture"
43 className="rounded-xl shadow-xl"
44 src={frontMatter.image}
45 width={frontMatter.imageDimensions.width / 2}
46 height={frontMatter.imageDimensions.height / 2}
47 blurDataURL={shimmer(1920, 1080)}
48 placeholder="blur"
49 />
50 </div>
51
52 <div className="mt-3 mb-10 px-2 max-w-5xl w-full blog-content">
53 {children}
54 </div>
55 <div className="flex items-center px-3 pb-3 gap-1 justify-end">
56 <RiEditBoxLine className="mt-0.5" />
57 <a
58 className="text-lg text-blue-600 duration-200 hover:text-blue-700 font-semibold"
59 href={getGitHubEditURL(frontMatter.slug)}
60 >
61 Edit on GitHub
62 </a>
63 </div>
64 <div className="p-3">
65 <BlogInteractions refetch={refetch} slug={frontMatter.slug} />
66 <BlogComments refetch={refetch} comments={comments} />
67 <div className="flex justify-center">
68 <BarLoader color="#60A5FA" width="42rem" loading={isLoading} />
69 </div>
70 </div>
71</article>

How simple is it to add a new post?

With all the advanced setup that we had to do before, you would think that it is very complicated to add a new post. But that's not the case! If you understand Markdown you just create a new files in the blog folder it will automatically be present in the blog page! This makes it as easy as when I used Ghost which is what I was aiming for. Nice!

Now, how can others interact with these posts?

As opposed to the blog itself, data such as the views or comments are not something that can be generated at build as they change when someone interacts with the blog. Fortunately, I have experience using MongoDB so I decided to use that to store my blog posts along with all of its data. However, I decided to keep all this logic as an API route in Next.JS as I wanted to try and keep the entire website running in one app.

I am using Monk which is essentially the more simpler version of well-known Mongoose.

MongoDB integration
1import monk from "monk";
2
3const db = monk(process.env.MONGO_URI);
4
5export default db;

I can then import this function anywhere where I would need it. For instance, I created basic API routes to add views, add/delete comments and so on. To then integrate it with the actual frontend I am using react-query to interact between the API and frontend.

Fetch comments
1const { isLoading, data, refetch } = useQuery(
2 `stats${frontMatter.slug}`,
3 () => {
4 return fetch(`/api/blog/get/${frontMatter.slug}`).then((res) =>
5 res.json()
6 );
7 }
8);

However, in order to have a proper comments system we would need to be able to store some user information on our database in order to identify who is actually commented.

However, luck is on my side! I discovered next-auth which is my holy grail to getting authentication done. I have integrated both Google and Discord and they both return the information that I would need to keep in my database.
NextAuth provider
1export default NextAuth({
2 providers: [
3 GoogleProvider({
4 clientId: process.env.GOOGLE_CLIENT_ID,
5 clientSecret: process.env.GOOGLE_CLIENT_SECRET,
6 }),
7 DiscordProvider({
8 clientId: process.env.DISCORD_CLIENT_ID,
9 clientSecret: process.env.DISCORD_SECRET,
10 }),
11 ],
12 secret: process.env.JWT_CODE,
13});
You can see this in action by scrolling down to the "What do you think?" section of the blog post.

Conclusion

Well then, thanks for reading my first (but detailed) blog post! If you have any opinions to what I can do to make this page feel better, feel free to send me an email in order to give me feedback. Cheers!

What do you think?

Leave a comment

Share your opinion regarding this post for other people to see.

Loading...