Preface
As I reflect on my front-end development career so far, I feel frustrated that I have no major accomplishments to showcase. Front-end technology evolves so rapidly. Whenever a new framework emerges, I eagerly examine and study it, yet I fail to translate this learning into tangible progress.
Therefore, I decided to create a personal blog site to chronicle my professional journey. This blog will empower me to grow by chronicling the process of putting my skills into practice through real projects. Potentially publishing on developer platforms can garner feedback and give me a sense of achievement.
I came across an inspiring blog site called onur.dev which exemplified the kind of site I wanted to build for myself. The clean design and features were just what I had envisioned. I resolved to model my own site after this one.
This is where i found this project:
Key Technologies Used
Key Learning
1.Buying a Domain Name
Previously, I never had my own domain. For this project, purchasing a domain was the first step. I felt this would motivate me to finish building the site - if I owned the domain, I'd be less likely to abandon the project.
After researching domain providers, I chose GoDaddy because it supports Alipay(It's one of the most popular payment in china). The process of connecting the domain to my Vercel site was smooth. This small win spurred me on.
2.Mastering Next.js Concepts
Although I'd used Next.js before, this project consolidated my knowledge.
For example, I implemented Next's generateMetadata API to optimize SEO, pulling content from Contentful. This SEO optimization was very new to me, and helped improve SEO a lot.
I also got familiar with generateStaticParams which builds dynamic routes into static pages at build time for better performance.
In addition, I enabled Draft Mode which allowed me to preview unpublished Contentful posts. This avoids having to rebuild static pages each time I update a draft.
The Link component was another revelation - I didn't know it preloads associated JS/CSS files by default for faster page transitions. However, this caused an issue with incorrectly counting page views which I send PR to origin project .
Finally, I utilized revalidatePath to refresh cached content based on the triggering of Contentful webhooks. This smart revalidation approach was very useful.
3.Headless CMS
This was my first time using a headless CMS in a real project. Contentful's GraphQL API made it simple and flexible to retrieve content as needed.
However, debugging queries was challenging at times as error messages aren't always clear. The JS library also made it hard to take advantage of TypeScript features.
A few UX issues in the Contentful UI itself also proved frustrating:
- The template selector often errored out when trying to use a template.
- The documentation was not easy to navigate - for example, finding how to pass dynamic params to webhooks involved a lot of trial and error.
But overall, Contentful hugely simplified my content workflow. I can focus on developing, not worrying about the backend. The convenience of managing content separately outweighed the debugging difficulties.
4.supabase
Implementing Supabase's RPC functionality was eye-opening. It encapsulates data logic on the server, while the client just calls remote procedures. This is excellent for security and maintainability.
export async function POST(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const slug = searchParams.get('slug')
if (!slug) return NextResponse.json({ error: 'Missing slug parameter' }, { status: 400 })
const res = await getPost(slug)
try {
await supabase.rpc('increment_view_count', { page_slug: slug, id: res.sys.id })
return NextResponse.json({ messsage: `View count incremented successfully for slug: ${slug}` }, { status: 200 })
} catch (error: any) {
console.error('Error incrementing view count:', error)
return NextResponse.json({ error: error.message }, { status: 500 })
}
}
Supabase's realtime sync also impressed me. Subscribing to database changes from the client is powerful yet simple to set up.For example, I implemented a viewer count for blog posts that updates in realtime as people read the post. This feature would have been much more complex without Supabase's streamlined syncing.
useEffect(() => {
function handleRealtimeChange(payload: any) {
if (payload?.new?.slug) {
setViewData((prev) => {
if (!prev) return null
const index = prev.findIndex((item) => item.slug === payload.new.slug)
index !== -1 ? (prev[index] = payload.new) : prev.push(payload.new)
return [...prev]
})
}
}
const channel = supabase
.channel('supabase_realtime')
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: SUPABASE_TABLE_NAME,
...(slug && { filter: `slug=eq.${slug}` })
},
handleRealtimeChange
)
.subscribe()
return () => {
supabase.removeChannel(channel)
}
Other Learnings
Here are some other useful libraries and techniques I learned on this project:
Using prettier-plugin-tailwindcss to automatically format Tailwind classes.
frame-motion for smooth CSS animations. In this project, this library made it easy to implement the subtle yet eye-catching effect of the view count fading in .
vaulfor displaying modals in a native-app style in mobile.
react-tweet makes it easy to embed tweets.
react-wrap-balancer makes titles more readable in different viewportsizes.
react-intersection-observer to detect when elements are visible in the viewport and optimize rendering.
import { PropsWithChildren } from "react";
import { useInView } from "react-intersection-observer";
interface ShowInViewProps {
rootMargin?: string;
triggerOnce?: boolean;
}
export const ShowInView = ({
children,
rootMargin = "0px",
triggerOnce = true,
...rest
}: PropsWithChildren<ShowInViewProps>) => {
const { ref, inView } = useInView({
rootMargin,
triggerOnce,
});
return (
<div ref={ref} data-role="intersection-observer" {...rest}>
{inView && children}
</div>
);
};
My Contributions
- Fixed an issue with incorrect view counts caused by Next.js preloading. Submitted a PR with the fix.
- Reached out to the original repo owner to discuss Next.js revalidation strategies.
- Submitted another PR to fix a small logic bug I found.
- Implemented auto-deletion of old view count records using Supabase RPC. This required storing the Contentful sys ID to link entries after deletion.
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
const secret = requestHeaders.get('x-vercel-reval-key')
if (secret !== process.env.CONTENTFUL_REVALIDATE_SECRET) {
return NextResponse.json({ message: 'Invalid secret' }, { status: 401 })
}
const data = await request.json()
const { id } = data
if (!id) return NextResponse.json({ error: 'Missing id parameter' }, { status: 400 })
try {
const res = await supabase.rpc('delete_view_count', { entry_id: id })
return NextResponse.json({ messsage: `View count Deleted successfully for id: ${id}` }, { status: 200 })
} catch (error: any) {
console.error('Error incrementing view count:', error)
return NextResponse.json({ error: error.message }, { status: 500 })
}
}
Conclusion
This project gave me a polished blog to share my journey. More importantly, it leveled up my React skills. I'm excited to apply these learnings to future ambitious projects.
If you find this article useful, I invite you to check out the👏✨🌹 original project that inspired me as well as my own implementation. I also welcome you to visit my blog and share any feedback🙇🙇♀️🫡!
Top comments (0)