Skip to main content
This example demonstrates a media-focused search experience optimized for browsing articles and images with infinite scroll, date-based filtering, and category navigation.

Overview

The media search example features:
  • Infinite scroll for seamless browsing
  • Image-heavy results with article previews
  • Date range filtering for time-based search
  • Category and author filters
  • Responsive card layout
  • Selected topics display
Media search interface

Implementation

JavaScript Example

Initialize the search with blog/media index:
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import instantsearch from 'instantsearch.js';
import { singleIndex } from 'instantsearch.js/es/lib/stateMappings';

const searchClient = algoliasearch(
  '1QDAWL72TQ',
  '47700f55d95d23f5a57744b9a027ea83'
);

const search = instantsearch({
  searchClient,
  indexName: 'PROD_algolia_blog',
  routing: {
    stateMapping: singleIndex('PROD_algolia_blog'),
  },
  insights: true,
});

Key Features

Infinite Scroll

Seamlessly load more results as users scroll:
const infiniteHits = connectInfiniteHits(
  ({ items, showMore, isLastPage, widgetParams }) => {
    const container = document.querySelector(widgetParams.container);
    
    // Intersection Observer for auto-loading
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting && !isLastPage) {
          showMore();
        }
      });
    });
    
    const loadMoreTrigger = document.createElement('div');
    container.appendChild(loadMoreTrigger);
    observer.observe(loadMoreTrigger);
  }
);

Category Hierarchies

Navigate through hierarchical categories:
import { hierarchicalMenu } from 'instantsearch.js/es/widgets';

hierarchicalMenu({
  container: '#categories',
  attributes: [
    'categories.lvl0',
    'categories.lvl1',
    'categories.lvl2',
  ],
  showMore: true,
})

Author Refinement

Filter by article authors:
import { refinementList } from 'instantsearch.js/es/widgets';

refinementList({
  container: '#authors',
  attribute: 'coauthors.nickname',
  searchable: true,
  showMore: true,
  limit: 5,
  showMoreLimit: 20,
})

Selected Topics

Display currently selected topic filters:
import { currentRefinements } from 'instantsearch.js/es/widgets';

currentRefinements({
  container: '#selected-topics',
  includedAttributes: ['topics'],
  transformItems(items) {
    return items.map(item => ({
      ...item,
      label: item.label.replace('topics: ', ''),
    }));
  },
})

Article Card Template

Create rich article previews with images and metadata:
function createArticleCard(hit) {
  const author = hit.coauthors?.[0];
  const date = formatDistanceToNow(
    hit.created_at_timestamp * 1000,
    { addSuffix: true }
  ).replace('about ', '');
  
  return `
    <article class="card">
      <a href="/blog/${hit.primary_category.slug}/${hit.slug}">
        <div class="card-image">
          <img 
            src="${hit.cloudinary_url}" 
            alt="${hit.title}"
            loading="lazy"
          />
        </div>
        
        <div class="card-content">
          <header>
            <span class="card-category">
              ${hit.primary_category.title}
            </span>
            <span class="card-date">${date}</span>
            
            <h2 class="card-title">
              ${highlightedTitle(hit)}
            </h2>
          </header>
          
          <p class="card-excerpt">
            ${snippetContent(hit)}
          </p>
          
          ${author ? `
            <footer class="card-footer">
              <img 
                src="${author.avatar_url}" 
                alt="${author.nickname}"
                class="author-avatar"
              />
              <div class="author-info">
                <span class="author-name">${author.nickname}</span>
                <span class="author-title">${author.job_title}</span>
              </div>
            </footer>
          ` : ''}
        </div>
      </a>
    </article>
  `;
}

Date Filtering Utilities

Implement human-readable date ranges:
const dateRanges = [
  {
    label: 'Last 24 hours',
    start: Date.now() - (24 * 60 * 60 * 1000),
    end: Date.now(),
  },
  {
    label: 'Last 7 days',
    start: Date.now() - (7 * 24 * 60 * 60 * 1000),
    end: Date.now(),
  },
  {
    label: 'Last 30 days',
    start: Date.now() - (30 * 24 * 60 * 60 * 1000),
    end: Date.now(),
  },
  {
    label: 'Last year',
    start: Date.now() - (365 * 24 * 60 * 60 * 1000),
    end: Date.now(),
  },
];

function createDateMenu() {
  return menu({
    container: '#dates',
    attribute: 'published_at_timestamp',
    limit: 10,
    transformItems(items) {
      return dateRanges.map(range => ({
        label: range.label,
        value: `${range.start}:${range.end}`,
        count: items.reduce((count, item) => {
          const timestamp = parseInt(item.value);
          return timestamp >= range.start && timestamp <= range.end
            ? count + item.count
            : count;
        }, 0),
      }));
    },
  });
}

Responsive Layout

Adapt the layout for different screen sizes:
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 2rem;
  padding: 2rem;
}

@media (max-width: 768px) {
  .card-grid {
    grid-template-columns: 1fr;
    gap: 1rem;
    padding: 1rem;
  }
  
  .card-image {
    aspect-ratio: 16 / 9;
  }
}

Performance Optimization

Lazy Loading Images

<img 
  src="${hit.cloudinary_url}" 
  alt="${hit.title}"
  loading="lazy"
  decoding="async"
/>

Windowing for Large Lists

For very long lists, consider implementing virtual scrolling:
import { connectInfiniteHits } from 'instantsearch.js/es/connectors';

const virtualInfiniteHits = connectInfiniteHits(
  ({ items, showMore }) => {
    // Only render visible items
    const visibleItems = items.slice(
      scrollTop / itemHeight,
      (scrollTop + viewportHeight) / itemHeight
    );
    
    renderItems(visibleItems);
  }
);

Running the Example

cd examples/js/media
yarn install
yarn start
Open http://localhost:3000 to view the example.

Customization Tips

Modify the grid columns and card design to match your content type (videos, images, documents).
Include additional fields like reading time, view count, or custom taxonomies in the card template.
Add a favorites feature using Algolia Insights to track user preferences.
Adjust the date range options to match your content publication patterns.

Source Code

JavaScript

View source on GitHub

Vue

View source on GitHub

Build docs developers (and LLMs) love