How I built this blog with Next.js and Sanity CMS
How I built this blog site.
As a developer, diving into a new project is pretty damn exciting. Recently, I tackled building a personal website using technologies that offered high performance and scalability.
In this post, I'll share my thoughts and process, from selecting the appropriate tools to effectively integrating them, as well as the lessons learned and best practices gathered along the way.
Here's the stack I used: Next.js 14, Typescript, Tailwind CSS, Framer Motion, Anime, React Hook Form, Zod, and Sanity CMS v3.
Why I Chose Next.js 14
I started by choosing Next.js as the front-end framework. It offers out-of-the-box features such as server-side rendering and static site generation, which are crucial for SEO and performance. Its file-based routing system and API routes also make it incredibly intuitive to quickly set up a full-fledged web application.
Next.js keeps improving, and the pace of development is staggering. This can sometimes be a drawback, especially when an update introduces breaking changes. The introduction of server actions in 14 made api routes for mutations a thing of the past.
My contact form uses a server action to push mutations to Sanity.
Styling with Tailwind CSS
I used Tailwind CSS for styling. Its utility-first approach makes it unique among CSS frameworks. It allows for rapid UI development and helps maintain consistency across designs without locking you into prescribed UI components like Bootstrap or Foundation.
The amount of stuff you can do with Tailwind is mind-blowing, from setting up varients to building templates; there's little you can't do with it. Integrating Tailwind with Next.js is automatic during the initial setup.
Although Tailwind may seem huge during development, it automatically removes unused styles during the build process, resulting in much smaller style sheets in production. The only drawback I find with Tailwind is that it clutters up your HTML with lots of classes, making it difficult to read.
Enhancing UI Interactions with Framer Motion
I integrated Framer Motion, an animation library that uses CSS transforms to make the UI interactions more engaging. It's simple to use; you tack "motion." onto any HTML element and add a few custom attributes. For example, adding a button with a hover effect requires only a few lines of code, thanks to Framer Motion's simple API.
Of course, you can go considerably deeper with it than just micro-interactions, like the reveal animation on the homepage. In future updates, I will definitely explore Framer Motion's advanced capabilities.
Additionally, the dot animations on the homepage are achieved with Anime.js, which is more suitable for complex animations.
Form Handling with React Hook Form and Zod
It is crucial to handle forms efficiently and securely in web applications. I selected React Hook Form to manage the form state and Zod to validate the schema. Zod is great for making validations declarative and tightly coupled with TypeScript for type safety.
But, in hindsight, I should have built the forms from scratch and used Zod for validation on its own. Since it's only a simple contact form, I didn't need all that extra weight and code.
Next.js 14 adds server actions, which makes route handling a million times easier—you don't need routes! However, there's a catch: server actions send FormData, not JSON, which means you have to convert your data to JSON before you mutate it.
React Hook Form isn't the best choice if you use form actions.
Using TypeScript for Code Reliability
I used to be afraid of Typescript. It seemed overly complex, and I didn't want to learn another JavaScript variant. Writing straight JS is definitely easier. But debugging a bunch of type errors is a headache, too. It sucks to write 200 lines of code only to have it crash and then spend the next 2 hours trying to figure out what went wrong.
Using TS is more work, but in the long run, it saves time by showing type errors in real-time in my editor. It also helps with autocompletion in my IDE (VScode) and gives Inellisense more to work with.
Lastly, it's optional; you don't have to type everything, and your code won't break if you don't. After all, Typescript is just JavaScript with some convenient extra features.
Choosing Sanity CMS for the Back-End
For the back-end, I selected Sanity CMS instead of a traditional database. Writing everything in Markdown is simpler and more performant, but I wanted to build a true decoupled, headless CMS.
Sanity was not easy to set up; the boilerplate code needed is astounding. However, it offers a super-friendly content editing experience. I plan to use Sanity as my content hub across all my applications. Right now, I also have content on Supabase and MongoDB. Storing site-specific data in a database makes sense. Having my content on a structured platform where I can distribute it to all my various projects also makes sense.
Creating content in a Word-like environment is also logical from an SEO standpoint because I can clearly delineate headings and meta-descriptions. Sanity uses GROQ to query its Content Lake. GROQ was easy to learn, and the Sanity Studio includes a GROQ playground called Vision to test your projection before dropping it into a function. I don't need to set up a Studio on every project, which would tank performance. Instead, I can create a schema specific to the project and set up endpoints for my content hub. That way, all content everywhere will be dynamic and centrally located.
Integrating Sanity with Next.js allowed me to fetch data server-side or statically at build time, optimizing performance and scalability.
Lessons Learned and Best Practices
Throughout this project, several lessons stood out:
- Start with a clear structure: Planning the project structure and component hierarchy early in the process can save much refactoring time later.
- Embrace utility-first CSS cautiously: While Tailwind CSS accelerates development, it's vital to use it judiciously to avoid cluttering HTML with too many utility classes.
- Animation with purpose: Framer Motion is powerful, but animations should be used sparingly and only when they enhance the user experience.
- Validation at every step: Using Zod with React Hook Form ensured data integrity, reducing potential security risks.
- Invest in a suitable CMS: Sanity CMS proved invaluable for content management but required some initial investment in setup and customization.
Conclusion
Building this website with Next.js 14, Tailwind CSS, Framer Motion, React Hook Form, Zod, TypeScript, and Sanity CMS was a fulfilling experience that pushed the boundaries of what I could achieve with modern web technologies. Combining these tools made the development process enjoyable and resulted in a high-quality, maintainable, scalable web application. For anyone embarking on a similar journey, I recommend embracing these technologies, keeping in mind the lessons learned and best practices that helped make this project a success.