Building a Minimalist Portfolio with SolidJS
Coming from React, SolidJS felt refreshingly lightweight and intuitive. No useEffect confusion, no dependency arrays to debug, no unexpected re-renders. Just write code that works the way you think it should.
Why SolidJS Felt Right
After years of React, SolidJS was like a breath of fresh air:
- Lightweight: Significantly smaller runtime - the difference is noticeable
- Intuitive reactivity: Signals just work - change a value, the UI updates, that's it
- No mental overhead: No useEffect rules, no stale closures, no dependency arrays
- Predictable performance: Fine-grained updates mean no surprise re-renders
The Architecture
This portfolio isn't just a static site - it's a carefully orchestrated system with some interesting technical decisions:
1. Dynamic Blog System with Build-Time Processing
Instead of runtime markdown parsing, I built a Vite plugin that processes blog posts at build time:
// Blog posts are processed once during build
const processFiles = () => {
const blogPosts = files
.filter(file => file.endsWith(".md"))
.map(file => {
const vfile = readSync(resolve("src/content/blog", file));
matter(vfile); // Parse frontmatter
return {
...vfile.data.matter,
slug: file.replace(/\.md$/, ""),
content: marked(fileContent) // Pre-render HTML
};
});
}The clever part? Blog content is pre-rendered to HTML and embedded as strings in the generated imports. Zero runtime markdown processing.
2. Fine-Grained Reactivity with Signals
Unlike React's component-level re-renders, SolidJS signals update exactly what needs updating:
// Only the selected post UI updates, not the entire component tree
const [selectedPost, setSelectedPost] = createSignal<string | null>(null);
// Dynamic component switching without wrapper elements
<Dynamic
component={selectedPost() ? BlogPost : BlogList}
posts={posts}
selectedSlug={selectedPost() || ""}
/>3. Smart Tab Transitions
Implementing smooth transitions without the complexity of React's Suspense or transition libraries:
<Transition mode="outin" appear>
<Switch>
<Match when={tab() === "about"}>
<div id="about"><AboutSection /></div>
</Match>
<Match when={tab() === "projects"}>
<div id="projects"><ProjectsSection /></div>
</Match>
</Switch>
</Transition>One Transition wrapper, multiple content switches. Clean and performant.
4. Time-Based Blog Grouping
The blog automatically groups posts by year and month, with current year posts shown by month:
const groupedPosts = () => {
const currentYear = new Date().getFullYear();
const groups = new Map<string, typeof posts>();
props.posts.forEach(post => {
const date = new Date(post.date);
const year = date.getFullYear();
const key = year === currentYear
? date.toLocaleDateString('en', { month: 'short' })
: year.toString();
groups.get(key)?.push(post) || groups.set(key, [post]);
});
return sortedGroups;
};Performance Optimizations
1. Syntax Highlighting Strategy
Instead of shipping a heavy syntax highlighter to every visitor, code blocks are highlighted at runtime only when viewed:
onMount(() => {
// Prism.js loads only when viewing a blog post with code
if (articleRef) {
Prism.highlightAllUnder(articleRef);
}
});2. Theme Persistence Without Flash
Dark mode that doesn't flash on page load - achieved through cookie-based SSR:
// Theme is determined server-side from cookies
const getTheme = () => {
const cookieTheme = parseCookie(
isServer ? getRequestEvent()?.request.headers.get("cookie") : document.cookie
);
return cookieTheme || "dark";
};3. Direct DOM Manipulation
SolidJS's approach means we can safely use refs without the ceremony:
let articleRef!: HTMLElement;
// Direct access, no useRef hook needed
<article ref={articleRef}>Lessons Learned
1. Compilation is Powerful
Moving work to build time isn't just about performance - it's about reliability. Blog posts are validated once, not on every page load.
2. Reactivity Can Be Simple
SolidJS proves that reactive programming doesn't need to be complex. Signals are intuitive: they hold values, they notify on change, components subscribe automatically.
3. Less Abstraction, More Control
Without Virtual DOM overhead, you work closer to the metal. This means better performance and more predictable behavior.
4. TypeScript Just Works
The integration between SolidJS and TypeScript is seamless. Props are typed, signals are typed, and the compiler catches mistakes at build time.
The Hidden Features
Some easter eggs and clever implementations:
- Quotes Modal: A dice icon that triggers a random wisdom quote - implemented with Portals for clean DOM structure
- File Watching: The blog plugin watches for changes and regenerates imports automatically
- Responsive Without Media Queries: Using Tailwind's responsive utilities for adaptive layouts
- Automatic Post Sorting: Posts are chronologically sorted at build time
Technical Stack
- SolidJS: The reactive UI library
- Vite: Build tool and dev server
- TypeScript: Type safety throughout
- Tailwind CSS: Utility-first styling
- Prism.js: Syntax highlighting (lazy-loaded)
- Marked: Markdown processing (build-time only)
- solid-transition-group: Smooth animations
What's Next?
This portfolio is more than a showcase - it's a testing ground for modern web development patterns. Future explorations might include:
- WebAssembly for compute-intensive features
- Service Workers for offline support
- View Transitions API for page animations
- Incremental Static Regeneration for blog posts
The Takeaway
Building with SolidJS reminded me why I love web development. It's not about the framework wars or the latest trends - it's about crafting experiences that are fast, elegant, and respectful of users' resources.
The difference in developer experience is stark. React taught me to think in terms of render cycles and effect dependencies. SolidJS lets me think in terms of reactive values and direct transformations.
Sometimes the best solution isn't adding more complexity - it's finding a tool that matches how you naturally think about UI.