BloggingWeb DevelopmentNext.JSMDX

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

[object Object]'s profile image
David Alexandru Ilie

/ June 24, 2021

1,197 words6 min read––– views

Post picture

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.

export const getServerSideProps: GetServerSideProps = async () => {
    const response = await fetch(`https://davidilie.com/api/agenda/job/github`);

    const repos = await response.json();
    return { props: { repos, revalidate: 600 } };
};

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:

export const InfoQuote = ({ children }) => (
    <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">
        <div
            className="text-center bg-gray-900 rounded-full w-10 h-10 flex items-center justify-center"
            style={{
                float: "left",
                position: "absolute",
                top: "-30px",
                left: "-20px",
            }}
        >
            <HiOutlineInformationCircle className="text-2xl text-blue-500" />
        </div>
        <div className="p-0 m-0 text-lg mb-3">{children}</div>
    </div>
);

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:

export async function getAllFilesFrontMatter() {
    const files = fs.readdirSync(
        path.join(process.cwd(), `src`, `data`, `blog`)
    );

    return files.reduce((allPosts, postSlug) => {
        const source = fs.readFileSync(
            path.join(process.cwd(), `src`, `data`, `blog`, postSlug),
            `utf8`
        );

        const slug = postSlug.replace(`.mdx`, ``);

        const { data } = matter(source);

        return [
            {
                ...data,
                slug: slug,
            },
            ...allPosts,
        ];
    }, []);
}

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.

export async function getStaticPaths() {
    const posts = await getFiles(`blog`);

    return {
        paths: posts.map((p) => ({
            params: {
                slug: p.replace(/\.mdx/, ``),
            },
        })),
        fallback: false,
    };
}

export async function getStaticProps({ params }) {
    const post = await getFileBySlug(`blog`, params.slug);

    return { props: { ...post } };
}

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 className="text-white flex flex-col justify-start pt-28 w-full min-h-screen mx-auto max-w-2xl">
    {frontMatter.tags && (
        <div className="flex w-full px-3 mb-4 justify-start flex-wrap">
            {frontMatter.tags.map((tag, i) => (
                <BlogBadge tag={tag} key={i.toString()} />
            ))}
        </div>
    )}
    <h1 className="2xl:text-5xl xl:text-5xl md:text-5xl lg:text-5xl text-4xl font-semibold px-4">
        {frontMatter.title}
    </h1>
    <div className="flex flex-wrap justify-between items-center max-w-2xl mx-auto mb-12 px-3 mt-5 w-full">
        <div className="flex items-center">
            <span className="inline-flex items-center justify-center py-2 text-xs font-bold leading-none rounded-md">
                <Image
                    className="rounded-full"
                    src={frontMatter.by.avatar}
                    width="25px"
                    height="25px"
                />
                <span className="ml-1 header-gradient text-lg mr-1">
                    {frontMatter.by.name}
                </span>
            </span>
            <h1 className="text-gray-300">
                {" / "}
                {format(
                    parseISO(frontMatter.publishedAt),
                    "MMMM dd, yyyy"
                )}
            </h1>
        </div>
        <h1>
            {frontMatter.wordCount.toLocaleString() + " words"}
            {``}
            {frontMatter.readingTime?.text}
            {``}
            <BlogViewCounter slug={frontMatter.slug} />
        </h1>
    </div>
    <Image
        alt="Post picture"
        className="rounded-2xl"
        src={frontMatter.image}
        width={1905 / 2}
        height={957 / 2}
        blurDataURL={shimmer(1920, 1080)}
        placeholder="blur"
    />
    <div className="mb-10 px-2 max-w-4xl w-full blog-content">
        {children}
    </div>
    <div className="flex items-center px-3 pb-3 gap-1 justify-end">
        <RiEditBoxLine className="mt-0.5" />
        <a
            className="text-lg text-blue-600 duration-200 hover:text-blue-700 font-semibold"
            href={getGitHubEditURL(frontMatter.slug)}
        >
            Edit on GitHub
        </a>
    </div>
    <div className="p-3">
        <BlogInteractions
            refetch={refetch}
            slug={frontMatter.slug}
        />
        <BlogComments refetch={refetch} comments={comments} />
        <div className="flex justify-center">
            <BarLoader
                color="#60A5FA"
                width="42rem"
                loading={isLoading}
            />
        </div>
    </div>
</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.

import monk from "monk";

const db = monk(process.env.MONGO_URI);

export 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.

const { isLoading, data, refetch } = useQuery(`stats${frontMatter.slug}`, () => {
        return fetch(`/api/blog/get/${frontMatter.slug}`).then((res) =>
            res.json()
        );
    }
);

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.
export default NextAuth({
    providers: [
        Providers.Google({
            clientId: process.env.GOOGLE_CLIENT_ID,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET,
            authorizationUrl:
                "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
        }),
        Providers.Discord({
            clientId: process.env.DISCORD_CLIENT_ID,
            clientSecret: process.env.DISCORD_SECRET,
            scope: "identify",
        }),
    ],
    database: process.env.MONGO_URI,
    secret: process.env.JWT_CODE,
    session: {
        jwt: true,
        maxAge: 30 * 24 * 60 * 60,
    },
    jwt: {
        signingKey: process.env.JWT_CODE,
    },
    callbacks: {
        redirect: async (url, baseUrl) => {
            return Promise.resolve(url);
        },
    },
});
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?

Comments