Improve Lazy Loading, Refactor Filtering via `data-tags` in Toolkit Layout

Summary

Optimized toolkit rendering by switching from tag-based URL filtering to in-page data-tags filtering, improving responsiveness and removing full page reloads. Also enabled lazy loading and async decoding for tool images to reduce initial load time.

Why Care

Previously, selecting a tag triggered a URL reload with query parameters (?tag=...). This caused the entire page to re-render, resetting scroll position, reloading all cards, and introducing latency. Switching to in-page data-tags filtering enables instant feedback and seamless UX. Lazy loading image assets further improves toolkit performance and lowers bandwidth use.

Implementation

Changes Made

🟒 Refactored Toolkit Filtering

  • Replaced tag-based URL filtering with in-place DOM filtering using data-tags on each .tool-card
  • Updated CardGrid.astro to ensure data-tags is passed in both ToolCard and BareToolCard renderings
  • Updated TagColumn.astro to:
    • Dispatch custom tagsUpdated event
    • Filter visible .tool-card elements by comparing data-tags with selected tags

🟒 Optimized Image Performance

  • Enabled loading="lazy" and decoding="async" on all <img> tags in ToolCard.astro
  • Preserved existing fallback logic for missing or broken images, with graceful degradation to:
    • Screenshot URLs
    • Placeholder fallback: https://i0.wp.com/port2flavors.com/wp-content/uploads/2022/07/placeholder-614.png

Affected Files

text
src/components/tool-components/
β”œβ”€β”€ ToolCard.astro        # + lazy loading, data-tag logic
β”œβ”€β”€ TagColumn.astro       # + client-side filtering & search
β”œβ”€β”€ BareToolCard.astro    # (minor updates, if used)
src/layouts/
β”œβ”€β”€ ToolkitLayout.astro   # removed filterTag prop, now passes all tools

Technical Details

data-tags Example from ToolCard.astro

astro
<ToolCard
  {...tool}
  filePath={tool.filePath}
  class="tool-card"
  data-tags={JSON.stringify(tool.tags || [])}
/>

Filtering Logic in TagColumn.astro (JS)

js
function filterCards() {
  allCards.forEach(card => {
    const tags = JSON.parse(card.dataset.tags || '[]');
    const match = selectedTags.every(tag => tags.includes(tag));
    card.style.display = match || selectedTags.length === 0 ? '' : 'none';
  });
}

Removed Prop Logic in ToolkitLayout.astro

astro
// Removed this:
const {
  ...
  filterTag
} = Astro.props;

const filteredEntries = filterTag
  ? toolEntries.filter(entry => entry.data.tags?.includes(filterTag))
  : toolEntries;
Now CardGrid is rendered unfiltered with all entries, and the filtering happens entirely client-side using JavaScript.

Integration Points

  • Ensures tag filters persist visually and functionally across paginated or dynamically styled content
  • Tag filtering now plays well with other layout JS (like mouse tracking and image fallbacks)
  • Removed dependency on server-rendered tag filters β€” no need to sync Astro route logic with query param state

Documentation

No external libraries added or removed. No breaking API changes introduced.