Introduction
Creating a personal portfolio website is more than just showcasing projects; it’s about demonstrating technical expertise through the implementation itself. This technical blog documents my journey of building a full-stack portfolio website using Next.js 15, React, and various modern web technologies. The project evolved from a simple static site concept into a comprehensive web application featuring dynamic interactions, real-time analytics, and a complementary blog platform.
Project Overview and Architecture
My portfolio website serves as a central hub for my professional presence, combining a modern single-page application with an integrated Jekyll blog for technical writing. The architecture follows a component-based design pattern, ensuring modularity and maintainability.
Core Technologies Stack
The foundation of this project rests on several carefully chosen technologies:
Next.js 15 with App Router: I selected Next.js as the primary framework for its server-side rendering capabilities, built-in optimization features, and excellent developer experience. The new App Router provides better performance through React Server Components and simplified routing patterns.
React 18: The component library handles all UI interactions, state management, and lifecycle operations. Using functional components with hooks allowed for cleaner, more maintainable code.
Tailwind CSS v4: For styling, Tailwind’s utility-first approach enabled rapid development while maintaining consistency. The new CSS-first configuration in version 4 aligned perfectly with Next.js 15’s architecture.
Framer Motion: Animation brings life to user interfaces. Framer Motion provided declarative animations that enhance user experience without compromising performance.
Technical Implementation Details
Component Architecture
The application follows a modular component structure where each section operates independently while sharing common utilities and styles. The main page component orchestrates seven primary sections: Navbar, Header, About, Services, Work, Contact, and Footer.
// Page composition with theme state management
export default function Home() {
const [isDarkMode, setIsDarkMode] = useState(false);
// Theme persistence logic
useEffect(() => {
if (localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)) {
setIsDarkMode(true);
}
}, []);
Dark Mode Implementation
One of the first features I implemented was a comprehensive dark mode system. Rather than using simple CSS filters, I created a proper theme architecture using CSS custom properties and Tailwind’s dark variant system. The theme preference persists across sessions using localStorage, and the system respects user’s OS-level preferences on first visit.
The implementation required careful consideration of color contrasts, especially for accessibility. Each component receives the isDarkMode prop, allowing for conditional rendering of theme-appropriate assets like logos and icons.
Animation System with Framer Motion
Creating smooth, performant animations was crucial for user engagement. I implemented scroll-triggered animations using Framer Motion’s whileInView property, ensuring elements animate as users scroll through the portfolio.
<motion.div
initial={{opacity:0, scale:0.9}}
whileInView={{opacity:1, scale:1}}
transition={{duration:0.6}}
>
During development, I encountered an issue where animations weren’t triggering. The problem arose from applying motion properties to regular HTML elements instead of motion components. Switching from <div>
to <motion.div>
resolved this issue immediately.
Form Handling and Email Integration
The contact form implementation uses Web3Forms API for serverless email handling. This choice eliminated the need for a backend server while providing reliable email delivery.
const onSubmit = async (event) => {
event.preventDefault();
const formData = new FormData(event.target);
formData.append("access_key", "YOUR_ACCESS_KEY");
const response = await fetch("https://api.web3forms.com/submit", {
method: "POST",
body: formData
});
Initially, I faced a 400 Bad Request error when submitting the form. The issue stemmed from incorrectly setting Content-Type headers and attempting to stringify FormData. Removing unnecessary headers and sending raw FormData resolved the problem.
Analytics Integration with Microsoft Clarity
Why Microsoft Clarity?
After researching various analytics platforms, I chose Microsoft Clarity over alternatives like Google Analytics or Hotjar for several reasons:
- Free tier with no limits: Unlike Hotjar’s restricted free plan, Clarity offers unlimited sessions and recordings
- Privacy-focused: Less intrusive than Google Analytics with better GDPR compliance
- Visual insights: Heatmaps and session recordings provide deeper understanding of user behavior
- Easy integration: Simple script injection without complex configuration
Custom Event Implementation
Beyond basic analytics, I implemented custom event tracking to monitor specific user interactions:
export const clarityEvents = {
contactFormSubmit: (success) =>
trackEvent("contact_form_submit", { success }),
projectView: (projectName) =>
trackEvent("project_view", { project: projectName }),
themeToggle: (isDarkMode) =>
trackEvent("theme_toggle", { theme: isDarkMode ? "dark" : "light" })
};
This granular tracking helps identify which projects attract the most attention, how users interact with forms, and their theme preferences.
Jekyll Blog Integration
Technical Architecture
The blog section runs as a separate Jekyll static site, hosted on GitHub Pages. This separation provides several advantages:
- Independent deployment cycles: Blog updates don’t require redeploying the main portfolio
- Better SEO: Static generation ensures fast loading and better search engine indexing
- Markdown support: Writing technical content in Markdown streamlines the publishing process
Integration Challenges and Solutions
Connecting the Jekyll blog with the main portfolio presented unique challenges. Initially, the service cards’ “Read more” links led nowhere. I solved this by dynamically linking to the deployed GitHub Pages URL:
<a href={link} target="_blank" rel="noopener noreferrer">
Read more
</a>
During Jekyll deployment, I encountered Ruby version conflicts. The Gemfile.lock required Ruby 3.2.0, but GitHub Actions used Ruby 3.1. Updating the workflow configuration resolved this:
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
Another issue arose from platform incompatibility. The Gemfile.lock was generated on Windows but GitHub Actions runs on Linux. Adding Linux platform support fixed this:
bundle lock --add-platform x86_64-linux
Deployment Strategy and Domain Configuration
Vercel vs. Netlify Decision
I chose Vercel over Netlify for several technical reasons:
- Next.js optimization: Vercel, created by the Next.js team, offers superior framework-specific optimizations
- Edge functions: Better support for serverless functions with edge runtime
- Analytics integration: Built-in Web Vitals monitoring without additional configuration
- Deployment speed: Faster build times through intelligent caching
DNS Configuration Challenges
Configuring the .com.np domain proved challenging. The initial DNS configuration pointed to incorrect nameservers, causing deployment failures. The solution involved:
- Accessing the domain registrar’s control panel
- Updating nameservers to Vercel’s DNS (ns1.vercel-dns.com, ns2.vercel-dns.com)
- Removing default domain aliases in Vercel dashboard
- Setting the custom domain as primary
This process taught me the importance of understanding DNS propagation and nameserver configuration.
Performance Optimization Strategies
Image Optimization
Next.js Image component automatically optimizes images through lazy loading, format conversion, and responsive sizing:
<Image
src={assets.profile_img}
alt="Profile"
className="rounded-full w-32"
/>
Font Loading Strategy
I implemented Google Fonts through Next.js font optimization, reducing layout shift and improving load times:
const outfit = Outfit({
subsets: ["latin"],
weight: ["400", "500", "600", "700"],
variable: "--font-outfit",
});
Code Splitting
The application leverages automatic code splitting through Next.js, ensuring users only load necessary JavaScript for each interaction.
Challenges and Problem-Solving
React Re-render Loop
One significant challenge was an infinite re-render loop in the navbar component. The issue occurred when directly calling setState in the onClick handler:
// Problem
<button onClick={setIsDarkMode(!isDarkMode)}>
// Solution
<button onClick={() => setIsDarkMode(prev => !prev)}>
This taught me the importance of using functional updates for state changes based on previous values.
Tailwind Configuration Issues
With Tailwind CSS v4’s new configuration approach, custom fonts weren’t applying correctly when defined in tailwind.config.mjs. Moving font definitions to globals.css resolved this:
.font-Ovo {
font-family: var(--font-ovo), serif;
}
Mobile Responsiveness
Ensuring proper mobile responsiveness required extensive testing across devices. The mobile menu implementation uses useRef for direct DOM manipulation, providing smooth slide animations:
const openMenu = () => {
sideMenuRef.current.style.transform = 'translateX(-16rem)'
}
Project Management and Documentation
Version Control Strategy
Throughout development, I maintained a clear Git workflow with meaningful commit messages. Each feature was developed incrementally with commits like:
- “Add dark mode toggle with localStorage persistence”
- “Implement Framer Motion scroll animations”
- “Fix contact form 400 error – remove unnecessary headers”
Code Documentation
Every component includes comprehensive comments explaining functionality, props, and implementation decisions. This approach ensures maintainability and helps other developers understand the codebase quickly.
Research and Learning
The project required extensive research into modern web development practices. I consulted official documentation for Next.js, React, and Framer Motion, along with community resources on Stack Overflow for specific issues. The Frontend Masters courses provided deeper understanding of React patterns and performance optimization.
Future Enhancements
While the current implementation meets all requirements, several enhancements could further improve the project:
- Dynamic PDF Generation: Replace static resume downloads with real-time PDF generation from current portfolio content
- Progressive Web App: Add service workers for offline functionality
- Internationalization: Support multiple languages for global reach
- CMS Integration: Connect a headless CMS for easier content updates
Conclusion
Building this portfolio website demonstrated the importance of choosing the right technology stack and understanding each component’s role in the overall architecture. The journey from concept to deployment involved numerous challenges, from debugging animation issues to configuring DNS settings, each providing valuable learning experiences.
The integration of modern tools like Microsoft Clarity for analytics and Web3Forms for contact handling shows how third-party services can enhance functionality without compromising performance. The dual-site approach with Jekyll for blogging proves that sometimes separation of concerns leads to better overall architecture.
This project stands as a testament to the power of modern web development tools and the importance of continuous learning in our field. Every bug fixed and every feature implemented contributed to both the final product and my growth as a developer.
The code is open-source and available on GitHub, welcoming contributions and serving as a reference for others embarking on similar projects. As web technologies continue to evolve, this portfolio will grow alongside them, always reflecting the current best practices and my expanding skill set.
For demo visit my portfolio site at: https://www.devkotasabin.com.np/