Redesigning My Portfolio: From Static to Interactive
Redesigning My Portfolio: From Static to Interactive
After years of using a basic static portfolio, I decided it was time for a complete redesign. Here's the journey from concept to launch.
The Problem with My Old Portfolio
My previous portfolio had several issues:
- Boring animations - Basic CSS transitions felt flat
- No personality - Generic template that could be anyone's site
- Hard to update - Static HTML meant editing was painful
- No blog - Nowhere to share thoughts and experiences
- Poor mobile experience - Responsive design was an afterthought
Design Goals
Before touching any code, I established clear goals:
1. Minimalist but Not Boring
I love minimalism, but I didn't want it to feel sterile. The solution? Motion-driven minimalism - clean layouts with delightful micro-interactions.
2. Professional yet Personal
As a Full-Stack AI Developer, I wanted to showcase technical expertise while maintaining a human touch. The balance came from:
- Professional structure and content
- Playful animations and interactions
- Personal touches (handwritten font for signature touches)
3. Performance First
No matter how beautiful the site is, it needs to be fast:
- Target: < 1s First Contentful Paint
- Bundle size: < 200KB initial JavaScript
- Lighthouse score: 95+ across all metrics
4. Easy to Maintain
Life's too short to spend hours updating your portfolio. Requirements:
- Blog posts as MDX files (write once, render anywhere)
- Type-safe code (TypeScript everywhere)
- Simple deployment (push to main = live)
Technology Choices
React Router 7
I chose React Router 7 over Next.js for several reasons:
// Clean, type-safe data loading
export async function loader() {
const posts = await getAllPosts();
return { posts };
}
export default function Blog({ loaderData }: Route.ComponentProps) {
const { posts } = loaderData; // Fully typed!
return <BlogList posts={posts} />;
}
Why I love it:
- File-based routing is intuitive
- Built-in SSR without configuration
- Excellent TypeScript support
- Simpler than Next.js for my needs
Tailwind CSS 4
The new @theme directive is a game-changer:
@theme {
--color-primary: #e36414;
--color-surface-1: #0a0a0a;
--shadow-glow: 0 0 15px rgba(227, 100, 20, 0.2);
}
No more CSS-in-JS complexity, just pure CSS custom properties with Tailwind's utility classes.
Framer Motion
For animations, nothing beats Framer Motion's spring physics:
<motion.div
whileHover={{
y: -4,
boxShadow: '0 8px 20px rgba(227, 100, 20, 0.1)',
transition: { type: 'spring', stiffness: 400, damping: 30 }
}}
>
Hover me!
</motion.div>
Every interaction feels natural and responsive.
Design Decisions
Color Palette
I went with a dark theme for several reasons:
- Reduces eye strain - Important for technical content
- Highlights content - Orange accent pops against black
- Modern aesthetic - Dark mode is the standard now
- Energy efficient - Better for OLED displays
The orange accent (#e36414) was chosen for its warmth and energy. It's professional but not corporate.
Typography
I use two fonts strategically:
- Geist Mono - For body text, creating a technical feel
- Inter - For headings, providing contrast and hierarchy
The monospace body font is unconventional but reinforces my identity as a developer.
Animation Philosophy
Every animation serves a purpose:
// Entrance animations - welcoming and smooth
const fadeInUp = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: springs.gentle },
};
// Hover effects - playful and responsive
const hoverLift = {
hover: { y: -4, transition: springs.snappy },
};
// Micro-interactions - quick feedback
const tapScale = {
scale: 0.98,
transition: springs.snappy,
};
Rules I followed:
- Animate only 1-2 elements per view (avoid chaos)
- Use spring physics (feels natural)
- Respect
prefers-reduced-motion - Keep timing under 500ms
Component Architecture
Each section is a self-contained component:
components/
├── Hero.tsx # Name, title, social links
├── Experience.tsx # Work history timeline
├── Projects.tsx # Project showcase cards
└── Skills.tsx # Tech stack grid
This makes it easy to:
- Reorder sections
- Update content independently
- Test components in isolation
Building the Blog
The blog was the most complex feature. I wanted:
- Write in Markdown/MDX
- Code syntax highlighting
- Type-safe frontmatter
- Fast build times
The solution:
// content/blog/my-post.mdx
---
title: "My Blog Post"
date: "2024-01-15"
excerpt: "A brief description"
tags: ["react", "typescript"]
---
# My Blog Post
Content with **formatting** and code:
```typescript
const hello = "world";
The MDX pipeline:
1. Write post with frontmatter
2. Gray-matter parses metadata
3. Rehype-pretty-code highlights syntax
4. Framer Motion animates entrance
## Performance Optimizations
### Code Splitting
React Router 7 automatically splits routes:
```bash
build/client/assets/home-CDghO-cS.js 166.94 kB
build/client/assets/blog-B4XjJ2rD.js 21.61 kB
The blog bundle only loads when needed!
Lazy Loading
Images and heavy components are lazy loaded:
const BlogPost = lazy(() => import('./BlogPost'));
<Suspense fallback={<Skeleton />}>
<BlogPost />
</Suspense>
Animation Performance
I only animate GPU-accelerated properties:
- ✅
transform,opacity,scale,rotate - ❌
width,height,background-color
This keeps animations at 60fps even on low-end devices.
Caching Strategy
// Cache blog posts for 1 hour
export const headers = () => ({
'Cache-Control': 'public, max-age=3600',
});
Deployment
Deploying to Cloudflare Pages is effortless:
pnpm run build
pnpm run deploy
The entire site deploys to 300+ edge locations worldwide in under 2 minutes.
Metrics
After launch, here are the results:
Lighthouse Scores:
- Performance: 98
- Accessibility: 100
- Best Practices: 100
- SEO: 100
Web Vitals:
- LCP: 0.8s
- FID: 8ms
- CLS: 0.001
Bundle Sizes:
- Initial JS: 190KB (gzipped: 60KB)
- Initial CSS: 32KB (gzipped: 6KB)
- Total Page Weight: ~250KB
Lessons Learned
What Went Well
- Starting with design tokens - Having colors and spacing defined early made development faster
- Component-first approach - Building components before pages kept code organized
- Animation library - Creating reusable animation constants saved tons of time
- TypeScript everywhere - Caught bugs before they reached production
What I'd Do Differently
- Plan the blog earlier - Adding it later required some refactoring
- More mobile testing - I focused on desktop first, mobile second
- Document as I build - Writing this post made me realize I should have documented decisions in real-time
Surprising Discoveries
- Tailwind 4 is incredible - The new
@themesystem is so much better - React Router 7 feels lighter - Simpler than Next.js for my use case
- Spring animations > everything - Users notice and appreciate the physics
What's Next?
Future improvements I'm planning:
- Add case studies for major projects
- Implement dark/light mode toggle
- Add search functionality to blog
- Create an RSS feed
- Add analytics (privacy-focused)
- Internationalization (English + Chinese)
Conclusion
Rebuilding my portfolio was one of the best investments I've made. It's not just a showcase - it's a sandbox for trying new technologies and a platform for sharing knowledge.
The key takeaways:
- Motion matters - Good animations elevate the entire experience
- Performance is a feature - Fast sites feel professional
- Modern tools rock - React Router 7 + Tailwind 4 + Framer Motion is a joy
- Iterate constantly - Your portfolio is never "done"
If you're thinking about redesigning your portfolio, do it! Start small, iterate quickly, and have fun with it.
View the Code
The entire portfolio is open source. Check out the code on GitHub to see how everything works under the hood.
Built with React Router 7, Tailwind CSS 4, and Framer Motion. Deployed on Cloudflare Pages.