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.
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 Example1export const getServerSideProps: GetServerSideProps = async () => {2 const response = await fetch(`https://davidilie.com/api/agenda/job/github`);34 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 Example1export 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 <div4 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?
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 MDX1export async function getAllFilesFrontMatter() {2 const files = fs.readdirSync(3 path.join(process.cwd(), `src`, `data`, `blog`)4 );56 return files.reduce((allPosts, postSlug) => {7 const source = fs.readFileSync(8 path.join(process.cwd(), `src`, `data`, `blog`, postSlug),9 `utf8`10 );1112 const slug = postSlug.replace(`.mdx`, ``);1314 const { data } = matter(source);1516 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 slug1export async function getStaticPaths() {2 const posts = await getFiles(`blog`);34 return {5 paths: posts.map((p) => ({6 params: {7 slug: p.replace(/\.mdx/, ``),8 },9 })),10 fallback: false,11 };12}1314export async function getStaticProps({ params }) {15 const post = await getFileBySlug(`blog`, params.slug);1617 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 Code1<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 <Image16 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 <Image42 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>5152 <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 <a58 className="text-lg text-blue-600 duration-200 hover:text-blue-700 font-semibold"59 href={getGitHubEditURL(frontMatter.slug)}60 >61 Edit on GitHub62 </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 integration1import monk from "monk";23const db = monk(process.env.MONGO_URI);45export 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 comments1const { 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.
NextAuth provider1export 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});
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!