← Back to blog

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:

  1. Quotes Modal: A dice icon that triggers a random wisdom quote - implemented with Portals for clean DOM structure
  2. File Watching: The blog plugin watches for changes and regenerates imports automatically
  3. Responsive Without Media Queries: Using Tailwind's responsive utilities for adaptive layouts
  4. 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.