Back to Blog

Airbnb Frontend System Architecture: Performance-First Design at Global Scale

1. Product Overview & Scale

Airbnb operates one of the most visually-rich web applications on the internet—a marketplace connecting 5+ million hosts with 150+ million users across 220+ countries. The frontend must render high-resolution photography, interactive maps, complex date pickers, and real-time pricing while maintaining sub-second perceived performance.

Scale Metrics:

  • 7+ million active listings with 100+ images each
  • 2+ billion guest arrivals since inception
  • 100+ million monthly active users
  • 62 supported languages
  • 220+ countries and regions
  • 40+ currencies with real-time conversion
  • Peak traffic: 10x normal during major holidays

The frontend challenge isn't just about rendering—it's about rendering fast while maintaining the emotional, trust-building visual experience that drives booking conversion. A 100ms delay in LCP correlates directly with booking abandonment.

┌─────────────────────────────────────────────────────────────────────┐
│                     AIRBNB TRAFFIC PATTERNS                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Requests/sec                                                       │
│      │                                          ┌──┐                │
│  50k │                                         ┌┤  ├┐  Holiday      │
│      │                                        ┌┤│  ││├┐ Peaks       │
│  40k │                                       ┌┤││  │││├┐            │
│      │                                      ┌┤│││  ││││├┐           │
│  30k │    Weekend    ┌──┐    Weekend       ┌┤││││  │││││├┐          │
│      │    Peaks     ┌┤  ├┐   Peaks        ┌┤│││││  ││││││├┐         │
│  20k │  ┌──┐  ┌──┐ ┌┤│  ││├┐  ┌──┐  ┌──┐ ┌┤││││││  │││││││├┐        │
│      │ ┌┤  ├──┤  ├─┤││  ││││──┤  ├──┤  ├─┤│││││││  ││││││││├─       │
│  10k │─┤│  │  │  │ │││  ││││  │  │  │  │ ││││││││  │││││││││ │──    │
│      │ ││  │  │  │ │││  ││││  │  │  │  │ ││││││││  │││││││││ │      │
│   0  └─┴┴──┴──┴──┴─┴┴┴──┴┴┴┴──┴──┴──┴──┴─┴┴┴┴┴┴┴┴──┴┴┴┴┴┴┴┴┴─┴──────│
│       Mon Tue Wed Thu Fri Sat Sun Mon ... Dec 20-Jan 5              │
│                                                                     │
│  Critical Insight: 3x traffic variance requires elastic rendering   │
└─────────────────────────────────────────────────────────────────────┘

2. Core Frontend Challenges

The Image-Heavy Performance Paradox

Airbnb's core value proposition is visual—users browse properties through photographs. Yet images are the single largest contributor to poor Core Web Vitals:

┌─────────────────────────────────────────────────────────────────────┐
│              PAGE WEIGHT BREAKDOWN (SEARCH RESULTS)                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Images        ████████████████████████████████████████  78%        │
│  JavaScript    ████████                                  16%        │
│  CSS           ██                                         3%        │
│  Fonts         █                                          2%        │
│  HTML          ░                                          1%        │
│                                                                     │
│  Total: ~3.2MB (unoptimized) → 890KB (optimized)                   │
│                                                                     │
│  Challenge: 20+ listing cards × 5 images each = 100+ images        │
│             Must load above-fold in <2.5s for good LCP             │
└─────────────────────────────────────────────────────────────────────┘

The Six Core Challenges

ChallengeImpactComplexity
Image LoadingLCP directly tied to hero image renderHigh - need blur placeholders, AVIF/WebP, responsive srcset
Map Integration500KB+ JS payload, blocks main threadCritical - MapBox/Google Maps lazy loading, clustering
Date PickerComplex calendar logic, availability checksMedium - virtual scrolling, memoization
Real-time PricingPrice changes based on dates/guestsHigh - optimistic UI, streaming updates
Search Filters50+ filter combinations, URL state syncHigh - debouncing, URL serialization
Layout StabilityImages, maps, dynamic content cause shiftsCritical - skeleton screens, aspect ratios

The Trust-Performance Balance

Unlike e-commerce where users trust the brand, Airbnb users must trust strangers' homes. High-quality imagery isn't optional—it's the foundation of the business model. This creates a fundamental tension:

// The core tension in Airbnb's frontend architecture
interface PerformanceTrustTradeoff {
  // High-res images build trust but hurt LCP
  imageQuality: 'high' | 'medium' | 'low';

  // More images per listing increase confidence but hurt TTI
  imagesPerCard: number; // Optimal: 5-7, Users want: 20+

  // Instant map interaction builds confidence but blocks main thread
  mapInteractivity: 'immediate' | 'deferred' | 'on-demand';

  // Reviews visible immediately vs lazy-loaded
  reviewsStrategy: 'ssr' | 'lazy' | 'intersection-observer';
}

// Airbnb's solution: Progressive enhancement with perceived performance
const OPTIMAL_CONFIG: PerformanceTrustTradeoff = {
  imageQuality: 'high',        // Never compromise
  imagesPerCard: 1,            // Show 1 fast, carousel on hover
  mapInteractivity: 'deferred', // 100ms delay after FCP
  reviewsStrategy: 'intersection-observer'
};

3. High-Level Frontend Architecture

Airbnb pioneered several patterns now considered industry standard, including the adoption of Server Components and their influential "Design Language System" (DLS).

┌─────────────────────────────────────────────────────────────────────────────┐
│                      AIRBNB FRONTEND ARCHITECTURE                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                         CDN EDGE LAYER                               │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌────────────┐  │   │
│  │  │  Fastly     │  │   Image     │  │   Static    │  │   HTML     │  │   │
│  │  │  (Primary)  │  │   CDN       │  │   Assets    │  │   Cache    │  │   │
│  │  │             │  │  (Thumbor)  │  │  (Hashed)   │  │  (5min)    │  │   │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └────────────┘  │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                      │                                      │
│                                      ▼                                      │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                      RENDERING LAYER (Node.js)                       │   │
│  │                                                                       │   │
│  │  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐   │   │
│  │  │  Hypernova       │  │  React Server    │  │  Streaming SSR   │   │   │
│  │  │  (Legacy Pages)  │  │  Components      │  │  (Critical Path) │   │   │
│  │  └──────────────────┘  └──────────────────┘  └──────────────────┘   │   │
│  │                                                                       │   │
│  │  ┌────────────────────────────────────────────────────────────────┐  │   │
│  │  │              REQUEST COALESCING & DEDUPLICATION                 │  │   │
│  │  │   • Batch GraphQL queries across components                     │  │   │
│  │  │   • Dedupe identical requests within 50ms window               │  │   │
│  │  │   • Priority queue: above-fold data first                      │  │   │
│  │  └────────────────────────────────────────────────────────────────┘  │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                      │                                      │
│                                      ▼                                      │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                        CLIENT APPLICATION                            │   │
│  │                                                                       │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌────────────┐  │   │
│  │  │   React 18  │  │   Design    │  │   State     │  │   Route    │  │   │
│  │  │   + RSC     │  │   Language  │  │   (Apollo   │  │   Based    │  │   │
│  │  │             │  │   System    │  │   + Zustand)│  │   Splitting│  │   │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └────────────┘  │   │
│  │                                                                       │   │
│  │  ┌────────────────────────────────────────────────────────────────┐  │   │
│  │  │                    PERFORMANCE LAYER                            │  │   │
│  │  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────────┐   │  │   │
│  │  │  │ Priority │  │  Idle    │  │ Resource │  │  Prefetch    │   │  │   │
│  │  │  │ Scheduler│  │  Until   │  │  Hints   │  │  Predictor   │   │  │   │
│  │  │  │          │  │  Urgent  │  │          │  │              │   │  │   │
│  │  │  └──────────┘  └──────────┘  └──────────┘  └──────────────┘   │  │   │
│  │  └────────────────────────────────────────────────────────────────┘  │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

The Niobe Framework

Airbnb built "Niobe," an internal framework that extends React with performance primitives:

// Niobe: Airbnb's performance-first React framework
import {
  createPage,
  PriorityBoundary,
  DeferredHydration,
  ResourceScheduler
} from '@airbnb/niobe';

// Page definition with explicit performance budgets
export default createPage({
  name: 'SearchResults',

  // Performance budgets enforced at build time
  budgets: {
    jsBundle: 150_000,      // 150KB max
    cssBundle: 30_000,      // 30KB max
    lcpTarget: 2500,        // 2.5s
    inpTarget: 200,         // 200ms
    clsTarget: 0.1,         // 0.1
  },

  // Critical data fetched server-side
  getServerData: async (context) => {
    const { searchParams } = context;

    // Parallel fetch with timeout
    const [listings, mapData] = await Promise.all([
      fetchListings(searchParams).timeout(800),
      fetchMapBounds(searchParams).timeout(400),
    ]);

    return { listings, mapData };
  },

  // Client-side data (non-blocking)
  getClientData: async (context) => {
    // Deferred until after hydration
    return {
      recommendations: fetchRecommendations(context.userId),
      recentSearches: fetchRecentSearches(context.userId),
    };
  },
});

4. Core Web Vitals Deep Dive

LCP Optimization Strategy

Airbnb's LCP element is almost always the hero listing image. Their optimization strategy is multi-layered:

┌─────────────────────────────────────────────────────────────────────────────┐
│                         LCP OPTIMIZATION PIPELINE                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  REQUEST                                                                    │
│     │                                                                       │
│     ▼                                                                       │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 1. EDGE OPTIMIZATION (0-50ms)                                        │   │
│  │    • HTML cached at edge with 5-min TTL                              │   │
│  │    • Critical CSS inlined (<14KB)                                    │   │
│  │    • <link rel="preload"> for LCP image injected at edge            │   │
│  │    • Early hints (103) for critical resources                        │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│     │                                                                       │
│     ▼                                                                       │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 2. HTML STREAMING (50-200ms)                                         │   │
│  │    • Stream <head> immediately with resource hints                   │   │
│  │    • Stream above-fold HTML before data resolves                     │   │
│  │    • Skeleton placeholders with correct aspect ratios                │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│     │                                                                       │
│     ▼                                                                       │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 3. IMAGE PIPELINE (200-800ms)                                        │   │
│  │    • BlurHash placeholder rendered immediately (< 1KB)               │   │
│  │    • AVIF served to supported browsers (40% smaller)                 │   │
│  │    • Responsive srcset with density descriptors                      │   │
│  │    • fetchpriority="high" on LCP image                              │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│     │                                                                       │
│     ▼                                                                       │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 4. RENDER OPTIMIZATION (800-1500ms)                                  │   │
│  │    • content-visibility: auto on below-fold content                  │   │
│  │    • Intersection Observer for lazy images                           │   │
│  │    • will-change hints for animated elements                         │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│     │                                                                       │
│     ▼                                                                       │
│  LCP TARGET: < 2.5s (p75)                                                  │
│  ACTUAL: 1.8s (desktop), 2.3s (mobile 4G)                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
// LCP-optimized image component
import { BlurHash } from '@airbnb/blur-hash';

interface OptimizedImageProps {
  src: string;
  blurHash: string;
  alt: string;
  width: number;
  height: number;
  priority?: boolean;
  sizes?: string;
}

export function OptimizedImage({
  src,
  blurHash,
  alt,
  width,
  height,
  priority = false,
  sizes = '100vw',
}: OptimizedImageProps) {
  const [loaded, setLoaded] = useState(false);
  const imgRef = useRef<HTMLImageElement>(null);

  // Generate responsive srcset
  const srcSet = useMemo(() => {
    const widths = [320, 640, 960, 1280, 1920, 2560];
    return widths
      .filter(w => w <= width * 2) // Don't upscale beyond 2x
      .map(w => `${generateImageUrl(src, w)} ${w}w`)
      .join(', ');
  }, [src, width]);

  // Format selection based on browser support
  const format = useMemo(() => {
    if (supportsAvif()) return 'avif';
    if (supportsWebp()) return 'webp';
    return 'jpg';
  }, []);

  return (
    <div
      className="image-container"
      style={{
        aspectRatio: `${width} / ${height}`,
        // Prevent CLS by reserving exact space
        width: '100%',
        position: 'relative',
        overflow: 'hidden',
      }}
    >
      {/* BlurHash placeholder - renders instantly */}
      <BlurHash
        hash={blurHash}
        width={width}
        height={height}
        punch={1}
        style={{
          position: 'absolute',
          inset: 0,
          opacity: loaded ? 0 : 1,
          transition: 'opacity 300ms ease-out',
        }}
      />

      {/* Actual image with all optimizations */}
      <img
        ref={imgRef}
        src={generateImageUrl(src, 960, format)}
        srcSet={srcSet}
        sizes={sizes}
        alt={alt}
        width={width}
        height={height}
        loading={priority ? 'eager' : 'lazy'}
        decoding={priority ? 'sync' : 'async'}
        fetchPriority={priority ? 'high' : 'auto'}
        onLoad={() => setLoaded(true)}
        style={{
          position: 'absolute',
          inset: 0,
          width: '100%',
          height: '100%',
          objectFit: 'cover',
          opacity: loaded ? 1 : 0,
          transition: 'opacity 300ms ease-out',
        }}
      />
    </div>
  );
}

// Image URL generator with CDN transformations
function generateImageUrl(
  src: string,
  width: number,
  format: 'avif' | 'webp' | 'jpg' = 'webp'
): string {
  const params = new URLSearchParams({
    w: width.toString(),
    f: format,
    q: format === 'avif' ? '60' : '80', // AVIF needs lower quality number
    fit: 'crop',
    auto: 'compress',
  });

  return `https://a0.muscache.com/im/pictures/${src}?${params}`;
}

INP (Interaction to Next Paint) Optimization

Airbnb's search page has numerous interactive elements—filters, map interactions, carousels, date pickers. Each must respond within 200ms:

// INP-optimized interaction handlers
import {
  startTransition,
  useDeferredValue,
  useTransition
} from 'react';

// Priority scheduler for interactions
class InteractionScheduler {
  private highPriorityQueue: Array<() => void> = [];
  private lowPriorityQueue: Array<() => void> = [];
  private isProcessing = false;

  // High priority: visual feedback (< 50ms)
  scheduleHighPriority(callback: () => void) {
    this.highPriorityQueue.push(callback);
    this.process();
  }

  // Low priority: data updates (< 200ms)
  scheduleLowPriority(callback: () => void) {
    this.lowPriorityQueue.push(callback);
    this.process();
  }

  private process() {
    if (this.isProcessing) return;
    this.isProcessing = true;

    // Use scheduler API if available, else requestIdleCallback
    if ('scheduler' in globalThis) {
      this.processWithScheduler();
    } else {
      this.processWithRIC();
    }
  }

  private async processWithScheduler() {
    // Process high priority immediately
    while (this.highPriorityQueue.length > 0) {
      const task = this.highPriorityQueue.shift()!;
      await scheduler.postTask(task, { priority: 'user-blocking' });
    }

    // Process low priority when idle
    while (this.lowPriorityQueue.length > 0) {
      const task = this.lowPriorityQueue.shift()!;
      await scheduler.postTask(task, { priority: 'background' });
    }

    this.isProcessing = false;
  }

  private processWithRIC() {
    // High priority in next microtask
    queueMicrotask(() => {
      while (this.highPriorityQueue.length > 0) {
        this.highPriorityQueue.shift()!();
      }
    });

    // Low priority in idle callback
    requestIdleCallback((deadline) => {
      while (
        this.lowPriorityQueue.length > 0 &&
        deadline.timeRemaining() > 5
      ) {
        this.lowPriorityQueue.shift()!();
      }

      if (this.lowPriorityQueue.length > 0) {
        this.processWithRIC(); // Continue in next idle period
      } else {
        this.isProcessing = false;
      }
    });
  }
}

const scheduler = new InteractionScheduler();

// Filter component with optimized INP
export function SearchFilters({
  filters,
  onFilterChange
}: SearchFiltersProps) {
  const [isPending, startTransition] = useTransition();
  const [localFilters, setLocalFilters] = useState(filters);

  // Deferred value for expensive renders
  const deferredFilters = useDeferredValue(localFilters);

  const handleFilterChange = useCallback((
    key: string,
    value: FilterValue
  ) => {
    // Immediate visual feedback (high priority)
    scheduler.scheduleHighPriority(() => {
      setLocalFilters(prev => ({ ...prev, [key]: value }));
    });

    // Actual data fetch (low priority, wrapped in transition)
    scheduler.scheduleLowPriority(() => {
      startTransition(() => {
        onFilterChange(key, value);
      });
    });
  }, [onFilterChange]);

  return (
    <div
      className="filters-container"
      style={{ opacity: isPending ? 0.7 : 1 }}
    >
      <PriceRangeFilter
        value={localFilters.priceRange}
        onChange={(v) => handleFilterChange('priceRange', v)}
      />
      <AmenitiesFilter
        value={localFilters.amenities}
        onChange={(v) => handleFilterChange('amenities', v)}
      />
      <PropertyTypeFilter
        value={localFilters.propertyType}
        onChange={(v) => handleFilterChange('propertyType', v)}
      />
      {/* ... more filters */}
    </div>
  );
}

CLS (Cumulative Layout Shift) Prevention

Layout shifts are particularly problematic on Airbnb due to dynamic content—images loading, maps initializing, prices updating:

// CLS prevention system
import { create } from 'zustand';

// Track all potential layout shift sources
interface LayoutStabilityState {
  reservedSpaces: Map<string, { width: number; height: number }>;
  loadedElements: Set<string>;

  reserveSpace: (id: string, dimensions: { width: number; height: number }) => void;
  markLoaded: (id: string) => void;
  isStable: (id: string) => boolean;
}

const useLayoutStability = create<LayoutStabilityState>((set, get) => ({
  reservedSpaces: new Map(),
  loadedElements: new Set(),

  reserveSpace: (id, dimensions) => {
    set(state => ({
      reservedSpaces: new Map(state.reservedSpaces).set(id, dimensions),
    }));
  },

  markLoaded: (id) => {
    set(state => ({
      loadedElements: new Set(state.loadedElements).add(id),
    }));
  },

  isStable: (id) => get().loadedElements.has(id),
}));

// Stable container component
interface StableContainerProps {
  id: string;
  aspectRatio?: number;
  minHeight?: number;
  children: React.ReactNode;
  onLoad?: () => void;
}

export function StableContainer({
  id,
  aspectRatio,
  minHeight,
  children,
  onLoad,
}: StableContainerProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const { reserveSpace, markLoaded, isStable } = useLayoutStability();

  // Reserve space on mount
  useLayoutEffect(() => {
    if (!containerRef.current) return;

    const rect = containerRef.current.getBoundingClientRect();
    reserveSpace(id, { width: rect.width, height: rect.height });
  }, [id, reserveSpace]);

  // Handle content load
  const handleContentLoad = useCallback(() => {
    markLoaded(id);
    onLoad?.();
  }, [id, markLoaded, onLoad]);

  return (
    <div
      ref={containerRef}
      className="stable-container"
      style={{
        // Prevent CLS with explicit dimensions
        aspectRatio: aspectRatio ? `${aspectRatio}` : undefined,
        minHeight: minHeight ? `${minHeight}px` : undefined,
        // Contain layout to prevent shifts affecting siblings
        contain: 'layout paint',
        // Reserve space even before content loads
        contentVisibility: isStable(id) ? 'visible' : 'auto',
      }}
      onLoad={handleContentLoad}
    >
      {children}
    </div>
  );
}

// Listing card with CLS prevention
export function ListingCard({ listing }: ListingCardProps) {
  return (
    <article className="listing-card">
      {/* Image with reserved aspect ratio */}
      <StableContainer
        id={`listing-image-${listing.id}`}
        aspectRatio={4 / 3}
      >
        <OptimizedImage
          src={listing.photos[0].url}
          blurHash={listing.photos[0].blurHash}
          alt={listing.title}
          width={listing.photos[0].width}
          height={listing.photos[0].height}
          sizes="(max-width: 744px) 100vw, (max-width: 1128px) 50vw, 33vw"
        />
      </StableContainer>

      {/* Content with fixed height skeleton */}
      <StableContainer
        id={`listing-content-${listing.id}`}
        minHeight={120}
      >
        <div className="listing-info">
          <h3>{listing.title}</h3>
          <p>{listing.location}</p>
          <PriceDisplay
            price={listing.price}
            currency={listing.currency}
          />
        </div>
      </StableContainer>
    </article>
  );
}
/* CSS strategies for CLS prevention */

/* Reserve space for images before load */
.listing-card img {
  aspect-ratio: 4 / 3;
  width: 100%;
  height: auto;
  object-fit: cover;
  background: var(--skeleton-color);
}

/* Prevent font-swap CLS */
@font-face {
  font-family: 'Airbnb Cereal';
  src: url('/fonts/airbnb-cereal.woff2') format('woff2');
  font-display: optional; /* Invisible period, no swap flash */
  size-adjust: 100.6%;    /* Match fallback metrics */
  ascent-override: 95%;
  descent-override: 25%;
  line-gap-override: 0%;
}

/* Fallback with matched metrics */
.text-content {
  font-family: 'Airbnb Cereal',
    /* Fallback stack with similar metrics */
    -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    Roboto,
    sans-serif;
}

/* Map container with fixed dimensions */
.map-container {
  aspect-ratio: 16 / 9;
  min-height: 400px;
  contain: strict;
  content-visibility: auto;
  contain-intrinsic-size: auto 400px;
}

/* Price skeleton matching final size */
.price-skeleton {
  width: 80px;
  height: 24px;
  border-radius: 4px;
  background: linear-gradient(
    90deg,
    var(--skeleton-color) 25%,
    var(--skeleton-highlight) 50%,
    var(--skeleton-color) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

5. Rendering Strategy & Hydration Architecture

Streaming SSR with Selective Hydration

Airbnb uses streaming SSR to get content to users faster, combined with selective hydration to prioritize interactive elements:

// Streaming SSR configuration
import { renderToPipeableStream } from 'react-dom/server';
import { createFromNodeStream } from 'react-server-dom-webpack/client';

interface StreamingRenderOptions {
  // Timeout before falling back to client render
  shellTimeout: number;
  // Elements to prioritize for hydration
  hydrationPriority: string[];
  // Progressive enhancement config
  progressiveEnhancement: {
    // Core content that must work without JS
    noJSFallback: boolean;
    // Features that enhance with JS
    enhancedFeatures: string[];
  };
}

export async function renderPage(
  request: Request,
  options: StreamingRenderOptions
): Promise<Response> {
  const url = new URL(request.url);

  // Start data fetching immediately (don't wait for render)
  const dataPromise = fetchPageData(url);

  // Create abort controller for timeout
  const abortController = new AbortController();
  const timeoutId = setTimeout(
    () => abortController.abort(),
    options.shellTimeout
  );

  return new Promise((resolve, reject) => {
    const { pipe, abort } = renderToPipeableStream(
      <App
        url={url}
        dataPromise={dataPromise}
        hydrationPriority={options.hydrationPriority}
      />,
      {
        bootstrapScripts: ['/static/js/main.js'],

        // Shell is ready - start streaming
        onShellReady() {
          clearTimeout(timeoutId);

          const headers = new Headers({
            'Content-Type': 'text/html; charset=utf-8',
            'Transfer-Encoding': 'chunked',
            // Enable early hints
            'Link': [
              '</static/js/main.js>; rel=preload; as=script',
              '</static/css/main.css>; rel=preload; as=style',
            ].join(', '),
          });

          const stream = new ReadableStream({
            start(controller) {
              pipe({
                write(chunk: Uint8Array) {
                  controller.enqueue(chunk);
                },
                end() {
                  controller.close();
                },
              });
            },
          });

          resolve(new Response(stream, { headers }));
        },

        // Shell errored - fall back to client render
        onShellError(error) {
          clearTimeout(timeoutId);

          resolve(new Response(
            renderClientFallback(url),
            {
              status: 500,
              headers: { 'Content-Type': 'text/html' },
            }
          ));
        },

        // All content ready (for bots/crawlers)
        onAllReady() {
          // Log for monitoring
          logRenderComplete(url);
        },

        // Handle errors in streaming content
        onError(error) {
          logRenderError(url, error);
        },
      }
    );

    // Handle timeout
    abortController.signal.addEventListener('abort', () => {
      abort();
      reject(new Error('Render timeout'));
    });
  });
}

Progressive Hydration Strategy

// Progressive hydration with priority queues
import { hydrateRoot } from 'react-dom/client';

type HydrationPriority = 'critical' | 'high' | 'medium' | 'low' | 'idle';

interface HydrationTask {
  id: string;
  priority: HydrationPriority;
  element: Element;
  component: React.ComponentType;
  props: Record<string, unknown>;
}

class HydrationScheduler {
  private queues: Map<HydrationPriority, HydrationTask[]> = new Map([
    ['critical', []],
    ['high', []],
    ['medium', []],
    ['low', []],
    ['idle', []],
  ]);

  private isHydrating = false;
  private hydratedElements = new Set<string>();

  // Priority order for processing
  private readonly priorityOrder: HydrationPriority[] = [
    'critical', 'high', 'medium', 'low', 'idle'
  ];

  schedule(task: HydrationTask) {
    if (this.hydratedElements.has(task.id)) return;

    const queue = this.queues.get(task.priority)!;
    queue.push(task);

    this.processQueues();
  }

  private async processQueues() {
    if (this.isHydrating) return;
    this.isHydrating = true;

    for (const priority of this.priorityOrder) {
      const queue = this.queues.get(priority)!;

      while (queue.length > 0) {
        const task = queue.shift()!;

        // Check if element is in viewport (for non-critical)
        if (priority !== 'critical' && !this.isInViewport(task.element)) {
          // Re-queue with lower priority
          const lowerPriority = this.getLowerPriority(priority);
          if (lowerPriority) {
            task.priority = lowerPriority;
            this.schedule(task);
          }
          continue;
        }

        await this.hydrateElement(task);

        // Yield to main thread between hydrations
        if (priority !== 'critical') {
          await this.yieldToMain();
        }
      }
    }

    this.isHydrating = false;
  }

  private async hydrateElement(task: HydrationTask) {
    const { id, element, component: Component, props } = task;

    // Mark hydration start for performance tracking
    performance.mark(`hydration-start-${id}`);

    try {
      hydrateRoot(element, <Component {...props} />);
      this.hydratedElements.add(id);

      // Track hydration performance
      performance.mark(`hydration-end-${id}`);
      performance.measure(
        `hydration-${id}`,
        `hydration-start-${id}`,
        `hydration-end-${id}`
      );
    } catch (error) {
      // Fall back to client render on hydration mismatch
      console.error(`Hydration failed for ${id}:`, error);
      element.innerHTML = '';
      const root = createRoot(element);
      root.render(<Component {...props} />);
    }
  }

  private isInViewport(element: Element): boolean {
    const rect = element.getBoundingClientRect();
    return (
      rect.top < window.innerHeight + 100 && // 100px threshold
      rect.bottom > -100
    );
  }

  private getLowerPriority(
    current: HydrationPriority
  ): HydrationPriority | null {
    const index = this.priorityOrder.indexOf(current);
    return index < this.priorityOrder.length - 1
      ? this.priorityOrder[index + 1]
      : null;
  }

  private yieldToMain(): Promise<void> {
    return new Promise(resolve => {
      if ('scheduler' in globalThis && 'yield' in scheduler) {
        scheduler.yield().then(resolve);
      } else {
        setTimeout(resolve, 0);
      }
    });
  }
}

// Usage in page component
const hydrationScheduler = new HydrationScheduler();

// Critical: search input, filters (immediate interaction)
hydrationScheduler.schedule({
  id: 'search-bar',
  priority: 'critical',
  element: document.getElementById('search-bar')!,
  component: SearchBar,
  props: { initialQuery: window.__INITIAL_QUERY__ },
});

// High: first visible listing cards
document.querySelectorAll('.listing-card:nth-child(-n+6)').forEach((el, i) => {
  hydrationScheduler.schedule({
    id: `listing-${i}`,
    priority: 'high',
    element: el,
    component: ListingCard,
    props: window.__LISTINGS__[i],
  });
});

// Medium: map (complex but not immediately interactive)
hydrationScheduler.schedule({
  id: 'map',
  priority: 'medium',
  element: document.getElementById('map-container')!,
  component: Map,
  props: { bounds: window.__MAP_BOUNDS__ },
});

// Low: below-fold listings
document.querySelectorAll('.listing-card:nth-child(n+7)').forEach((el, i) => {
  hydrationScheduler.schedule({
    id: `listing-${i + 6}`,
    priority: 'low',
    element: el,
    component: ListingCard,
    props: window.__LISTINGS__[i + 6],
  });
});

// Idle: footer, secondary navigation
hydrationScheduler.schedule({
  id: 'footer',
  priority: 'idle',
  element: document.getElementById('footer')!,
  component: Footer,
  props: {},
});

6. Image Pipeline Architecture

Images are the single largest factor in Airbnb's performance. Their image pipeline is a sophisticated system handling 500M+ images:

┌─────────────────────────────────────────────────────────────────────────────┐
│                        AIRBNB IMAGE PIPELINE                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  UPLOAD                           PROCESSING                                │
│  ┌─────────┐                     ┌─────────────────────────────────────┐   │
│  │ Host    │──── Original ────▶ │ Thumbor Processing Cluster          │   │
│  │ Upload  │     (10-50MB)      │                                     │   │
│  │         │                     │ ┌─────────┐ ┌─────────┐ ┌────────┐ │   │
│  └─────────┘                     │ │ Resize  │ │ Format  │ │Quality │ │   │
│                                  │ │ (12     │ │ Convert │ │Optimize│ │   │
│                                  │ │ sizes)  │ │ AVIF/   │ │ (SSIM) │ │   │
│                                  │ │         │ │ WebP/   │ │        │ │   │
│                                  │ │         │ │ JPG     │ │        │ │   │
│                                  │ └─────────┘ └─────────┘ └────────┘ │   │
│                                  └─────────────────────────────────────┘   │
│                                               │                             │
│                                               ▼                             │
│  STORAGE                         ┌─────────────────────────────────────┐   │
│  ┌─────────────────────────────┐│           CDN EDGE CACHE            │   │
│  │ S3 Origin                   ││                                     │   │
│  │ • Original preserved        ││ ┌─────────┐ ┌─────────┐ ┌────────┐ │   │
│  │ • All variants generated    ││ │ Fastly  │ │ Cloud   │ │ Akamai │ │   │
│  │ • ~36 variants per image    ││ │ (US/EU) │ │ Front   │ │ (APAC) │ │   │
│  │ • BlurHash stored           ││ │         │ │ (LATAM) │ │        │ │   │
│  └─────────────────────────────┘│ └─────────┘ └─────────┘ └────────┘ │   │
│                                  └─────────────────────────────────────┘   │
│                                               │                             │
│                                               ▼                             │
│  CLIENT SELECTION                                                           │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Format Selection:                                                    │   │
│  │   Accept: image/avif → AVIF (40% smaller)                           │   │
│  │   Accept: image/webp → WebP (25% smaller)                           │   │
│  │   Fallback → Progressive JPEG                                        │   │
│  │                                                                       │   │
│  │ Size Selection (srcset):                                             │   │
│  │   320w  → Mobile portrait (1x)                                       │   │
│  │   640w  → Mobile landscape / portrait (2x)                           │   │
│  │   960w  → Tablet (1x) / Mobile (3x)                                  │   │
│  │   1280w → Tablet (2x) / Desktop small                                │   │
│  │   1920w → Desktop HD                                                 │   │
│  │   2560w → Desktop 4K                                                 │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
// Image service with intelligent format and size selection
interface ImageConfig {
  baseUrl: string;
  defaultQuality: number;
  formatPreferences: ('avif' | 'webp' | 'jpg')[];
  densityBreakpoints: number[];
}

class ImageService {
  private config: ImageConfig;
  private formatSupport: Map<string, boolean> = new Map();
  private connectionType: string = '4g';

  constructor(config: ImageConfig) {
    this.config = config;
    this.detectFormatSupport();
    this.detectConnectionType();
  }

  private async detectFormatSupport() {
    // AVIF support detection
    const avifSupport = await this.testFormat(
      'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKBzgABpAQ0AIAFEAAAPMhyRQ='
    );
    this.formatSupport.set('avif', avifSupport);

    // WebP support detection
    const webpSupport = await this.testFormat(
      'data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA=='
    );
    this.formatSupport.set('webp', webpSupport);
  }

  private testFormat(dataUri: string): Promise<boolean> {
    return new Promise(resolve => {
      const img = new Image();
      img.onload = () => resolve(true);
      img.onerror = () => resolve(false);
      img.src = dataUri;
    });
  }

  private detectConnectionType() {
    if ('connection' in navigator) {
      const conn = (navigator as any).connection;
      this.connectionType = conn.effectiveType || '4g';

      conn.addEventListener('change', () => {
        this.connectionType = conn.effectiveType || '4g';
      });
    }
  }

  // Get optimal format based on support and connection
  getOptimalFormat(): 'avif' | 'webp' | 'jpg' {
    // On slow connections, prefer WebP over AVIF (faster decode)
    if (this.connectionType === '2g' || this.connectionType === 'slow-2g') {
      if (this.formatSupport.get('webp')) return 'webp';
      return 'jpg';
    }

    // Normal connections: prefer AVIF for best compression
    if (this.formatSupport.get('avif')) return 'avif';
    if (this.formatSupport.get('webp')) return 'webp';
    return 'jpg';
  }

  // Get quality based on connection type
  getOptimalQuality(): number {
    switch (this.connectionType) {
      case 'slow-2g':
      case '2g':
        return 40;
      case '3g':
        return 60;
      case '4g':
      default:
        return 80;
    }
  }

  // Generate responsive image attributes
  generateImageProps(
    imageId: string,
    options: {
      width: number;
      height: number;
      sizes: string;
      priority?: boolean;
    }
  ): {
    src: string;
    srcSet: string;
    sizes: string;
    loading: 'eager' | 'lazy';
    decoding: 'sync' | 'async';
    fetchPriority: 'high' | 'low' | 'auto';
  } {
    const format = this.getOptimalFormat();
    const quality = this.getOptimalQuality();

    // Generate srcset for all density breakpoints
    const srcSet = this.config.densityBreakpoints
      .filter(w => w <= options.width * 3) // Up to 3x density
      .map(w => {
        const url = this.buildUrl(imageId, {
          width: w,
          format,
          quality
        });
        return `${url} ${w}w`;
      })
      .join(', ');

    // Default src at 1x size
    const src = this.buildUrl(imageId, {
      width: options.width,
      format,
      quality,
    });

    return {
      src,
      srcSet,
      sizes: options.sizes,
      loading: options.priority ? 'eager' : 'lazy',
      decoding: options.priority ? 'sync' : 'async',
      fetchPriority: options.priority ? 'high' : 'auto',
    };
  }

  private buildUrl(
    imageId: string,
    params: { width: number; format: string; quality: number }
  ): string {
    const searchParams = new URLSearchParams({
      im_w: params.width.toString(),
      im_format: params.format,
      im_q: params.quality.toString(),
    });

    return `${this.config.baseUrl}/${imageId}?${searchParams}`;
  }
}

// Usage
const imageService = new ImageService({
  baseUrl: 'https://a0.muscache.com/im/pictures',
  defaultQuality: 80,
  formatPreferences: ['avif', 'webp', 'jpg'],
  densityBreakpoints: [320, 640, 960, 1280, 1920, 2560],
});

// In component
function ListingImage({ photo, priority }: { photo: Photo; priority: boolean }) {
  const imageProps = imageService.generateImageProps(photo.id, {
    width: photo.width,
    height: photo.height,
    sizes: '(max-width: 744px) 100vw, (max-width: 1128px) 50vw, 33vw',
    priority,
  });

  return (
    <img
      {...imageProps}
      alt={photo.alt}
      width={photo.width}
      height={photo.height}
      style={{ aspectRatio: `${photo.width} / ${photo.height}` }}
    />
  );
}

7. Map Integration Performance

Maps are the second-largest performance bottleneck after images. Airbnb's approach involves aggressive lazy loading and clustering:

// Map loading strategy with performance optimization
import { lazy, Suspense, useEffect, useState } from 'react';

// Lazy load map library only when needed
const MapboxMap = lazy(() =>
  import(/* webpackChunkName: "mapbox" */ './MapboxMap')
);

interface MapContainerProps {
  bounds: LatLngBounds;
  listings: Listing[];
  onBoundsChange: (bounds: LatLngBounds) => void;
}

export function MapContainer({
  bounds,
  listings,
  onBoundsChange
}: MapContainerProps) {
  const [shouldLoad, setShouldLoad] = useState(false);
  const [isInteracted, setIsInteracted] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);

  // Load map when:
  // 1. Container is in viewport, AND
  // 2. User has interacted OR 2 seconds have passed
  useEffect(() => {
    if (!containerRef.current) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          // Start 2-second timer
          const timer = setTimeout(() => setShouldLoad(true), 2000);

          // Or load immediately on interaction
          const handleInteraction = () => {
            clearTimeout(timer);
            setShouldLoad(true);
            setIsInteracted(true);
          };

          containerRef.current?.addEventListener('mouseenter', handleInteraction);
          containerRef.current?.addEventListener('touchstart', handleInteraction);

          return () => {
            clearTimeout(timer);
            containerRef.current?.removeEventListener('mouseenter', handleInteraction);
            containerRef.current?.removeEventListener('touchstart', handleInteraction);
          };
        }
      },
      { rootMargin: '100px' }
    );

    observer.observe(containerRef.current);
    return () => observer.disconnect();
  }, []);

  // Preload map chunk on hover before actual load
  const preloadMap = useCallback(() => {
    import(/* webpackChunkName: "mapbox" */ './MapboxMap');
  }, []);

  return (
    <div
      ref={containerRef}
      className="map-container"
      onMouseEnter={preloadMap}
      style={{
        aspectRatio: '16 / 9',
        minHeight: '400px',
        contain: 'strict',
      }}
    >
      {shouldLoad ? (
        <Suspense fallback={<MapSkeleton bounds={bounds} listings={listings} />}>
          <MapboxMap
            bounds={bounds}
            listings={listings}
            onBoundsChange={onBoundsChange}
            interactive={isInteracted}
          />
        </Suspense>
      ) : (
        <MapSkeleton
          bounds={bounds}
          listings={listings}
          onClick={() => {
            setShouldLoad(true);
            setIsInteracted(true);
          }}
        />
      )}
    </div>
  );
}

// Static map skeleton that looks like a real map
function MapSkeleton({
  bounds,
  listings,
  onClick,
}: {
  bounds: LatLngBounds;
  listings: Listing[];
  onClick?: () => void;
}) {
  // Generate static map image URL
  const staticMapUrl = useMemo(() => {
    const center = bounds.getCenter();
    return `https://api.mapbox.com/styles/v1/airbnb/static/${center.lng},${center.lat},12,0/800x600@2x?access_token=${MAPBOX_TOKEN}`;
  }, [bounds]);

  // Pre-calculate marker positions for static display
  const markerPositions = useMemo(() => {
    return listings.slice(0, 20).map(listing => ({
      id: listing.id,
      x: longitudeToPixel(listing.lng, bounds),
      y: latitudeToPixel(listing.lat, bounds),
      price: listing.price,
    }));
  }, [listings, bounds]);

  return (
    <div
      className="map-skeleton"
      onClick={onClick}
      role="button"
      aria-label="Click to load interactive map"
      style={{ cursor: 'pointer' }}
    >
      {/* Static map background */}
      <img
        src={staticMapUrl}
        alt="Map showing listing locations"
        loading="lazy"
        style={{ width: '100%', height: '100%', objectFit: 'cover' }}
      />

      {/* Static price markers */}
      <div className="static-markers">
        {markerPositions.map(marker => (
          <div
            key={marker.id}
            className="price-marker static"
            style={{
              position: 'absolute',
              left: `${marker.x}%`,
              top: `${marker.y}%`,
              transform: 'translate(-50%, -50%)',
            }}
          >
            ${marker.price}
          </div>
        ))}
      </div>

      {/* Interaction prompt */}
      <div className="map-interaction-hint">
        Click to explore map
      </div>
    </div>
  );
}

Map Marker Clustering for Performance

// Supercluster-based marker clustering
import Supercluster from 'supercluster';
import { useMemo, useCallback } from 'react';

interface ClusterFeature {
  type: 'Feature';
  geometry: { type: 'Point'; coordinates: [number, number] };
  properties: {
    cluster: boolean;
    cluster_id?: number;
    point_count?: number;
    listing?: Listing;
  };
}

function useMarkerClusters(
  listings: Listing[],
  bounds: LatLngBounds,
  zoom: number
) {
  // Initialize supercluster with listings
  const supercluster = useMemo(() => {
    const cluster = new Supercluster({
      radius: 60,      // Cluster radius in pixels
      maxZoom: 16,     // Max zoom to cluster at
      minPoints: 2,    // Minimum points to form a cluster
      extent: 512,     // Tile extent
      nodeSize: 64,    // KD-tree node size
    });

    // Convert listings to GeoJSON features
    const features: ClusterFeature[] = listings.map(listing => ({
      type: 'Feature',
      geometry: {
        type: 'Point',
        coordinates: [listing.lng, listing.lat],
      },
      properties: {
        cluster: false,
        listing,
      },
    }));

    cluster.load(features);
    return cluster;
  }, [listings]);

  // Get clusters for current viewport
  const clusters = useMemo(() => {
    const bbox: [number, number, number, number] = [
      bounds.west,
      bounds.south,
      bounds.east,
      bounds.north,
    ];

    return supercluster.getClusters(bbox, Math.floor(zoom));
  }, [supercluster, bounds, zoom]);

  // Expand cluster on click
  const expandCluster = useCallback((clusterId: number) => {
    const expansionZoom = supercluster.getClusterExpansionZoom(clusterId);
    const center = supercluster
      .getLeaves(clusterId, 1)[0]
      .geometry.coordinates;

    return { zoom: expansionZoom, center };
  }, [supercluster]);

  return { clusters, expandCluster };
}

// Marker component with virtualization
function MapMarkers({
  clusters,
  visibleBounds,
  onClusterClick,
  onListingClick,
}: MapMarkersProps) {
  // Only render markers in visible viewport + buffer
  const visibleMarkers = useMemo(() => {
    const buffer = 0.1; // 10% buffer
    return clusters.filter(cluster => {
      const [lng, lat] = cluster.geometry.coordinates;
      return (
        lng >= visibleBounds.west - buffer &&
        lng <= visibleBounds.east + buffer &&
        lat >= visibleBounds.south - buffer &&
        lat <= visibleBounds.north + buffer
      );
    });
  }, [clusters, visibleBounds]);

  return (
    <>
      {visibleMarkers.map(feature => {
        if (feature.properties.cluster) {
          return (
            <ClusterMarker
              key={`cluster-${feature.properties.cluster_id}`}
              coordinates={feature.geometry.coordinates}
              count={feature.properties.point_count!}
              onClick={() => onClusterClick(feature.properties.cluster_id!)}
            />
          );
        }

        return (
          <ListingMarker
            key={`listing-${feature.properties.listing!.id}`}
            listing={feature.properties.listing!}
            onClick={() => onListingClick(feature.properties.listing!)}
          />
        );
      })}
    </>
  );
}

8. Search & Filter Performance

Search is Airbnb's most interaction-heavy page. Every filter change, date selection, or map pan triggers complex state updates:

// Search state machine with performance optimizations
import { createMachine, assign } from 'xstate';
import { useMachine } from '@xstate/react';

interface SearchContext {
  query: string;
  location: GeoLocation | null;
  dates: { checkIn: Date | null; checkOut: Date | null };
  guests: { adults: number; children: number; infants: number };
  filters: FilterState;
  results: Listing[];
  mapBounds: LatLngBounds | null;
  isMapView: boolean;
  pagination: { page: number; hasMore: boolean };
}

type SearchEvent =
  | { type: 'SET_QUERY'; query: string }
  | { type: 'SET_LOCATION'; location: GeoLocation }
  | { type: 'SET_DATES'; dates: SearchContext['dates'] }
  | { type: 'SET_GUESTS'; guests: SearchContext['guests'] }
  | { type: 'SET_FILTER'; key: string; value: FilterValue }
  | { type: 'CLEAR_FILTERS' }
  | { type: 'SET_MAP_BOUNDS'; bounds: LatLngBounds }
  | { type: 'TOGGLE_MAP_VIEW' }
  | { type: 'LOAD_MORE' }
  | { type: 'SEARCH_SUCCESS'; results: Listing[]; hasMore: boolean }
  | { type: 'SEARCH_ERROR'; error: Error };

const searchMachine = createMachine<SearchContext, SearchEvent>({
  id: 'search',
  initial: 'idle',

  context: {
    query: '',
    location: null,
    dates: { checkIn: null, checkOut: null },
    guests: { adults: 1, children: 0, infants: 0 },
    filters: defaultFilters,
    results: [],
    mapBounds: null,
    isMapView: false,
    pagination: { page: 1, hasMore: true },
  },

  states: {
    idle: {
      on: {
        SET_QUERY: {
          actions: 'updateQuery',
          target: 'debouncing',
        },
        SET_LOCATION: {
          actions: 'updateLocation',
          target: 'searching',
        },
        SET_DATES: {
          actions: 'updateDates',
          target: 'searching',
        },
        SET_GUESTS: {
          actions: 'updateGuests',
          target: 'searching',
        },
        SET_FILTER: {
          actions: 'updateFilter',
          target: 'debouncing',
        },
        SET_MAP_BOUNDS: {
          actions: 'updateMapBounds',
          target: 'debouncing',
        },
        LOAD_MORE: {
          target: 'loadingMore',
        },
      },
    },

    // Debounce rapid changes (typing, filter toggles)
    debouncing: {
      after: {
        300: 'searching', // 300ms debounce
      },
      on: {
        // Cancel debounce on new input
        SET_QUERY: {
          actions: 'updateQuery',
          target: 'debouncing',
        },
        SET_FILTER: {
          actions: 'updateFilter',
          target: 'debouncing',
        },
        SET_MAP_BOUNDS: {
          actions: 'updateMapBounds',
          target: 'debouncing',
        },
      },
    },

    searching: {
      invoke: {
        src: 'performSearch',
        onDone: {
          actions: 'setResults',
          target: 'idle',
        },
        onError: {
          actions: 'setError',
          target: 'error',
        },
      },
    },

    loadingMore: {
      invoke: {
        src: 'loadMoreResults',
        onDone: {
          actions: 'appendResults',
          target: 'idle',
        },
        onError: {
          target: 'idle',
        },
      },
    },

    error: {
      on: {
        '*': 'debouncing', // Any action retries search
      },
    },
  },
}, {
  actions: {
    updateQuery: assign({ query: (_, event) => event.query }),
    updateLocation: assign({ location: (_, event) => event.location }),
    updateDates: assign({ dates: (_, event) => event.dates }),
    updateGuests: assign({ guests: (_, event) => event.guests }),
    updateFilter: assign({
      filters: (context, event) => ({
        ...context.filters,
        [event.key]: event.value,
      }),
    }),
    updateMapBounds: assign({ mapBounds: (_, event) => event.bounds }),
    setResults: assign({
      results: (_, event) => event.data.results,
      pagination: (_, event) => ({ page: 1, hasMore: event.data.hasMore }),
    }),
    appendResults: assign({
      results: (context, event) => [...context.results, ...event.data.results],
      pagination: (context, event) => ({
        page: context.pagination.page + 1,
        hasMore: event.data.hasMore,
      }),
    }),
  },

  services: {
    performSearch: async (context) => {
      // Build search params from context
      const params = buildSearchParams(context);

      // Parallel fetch: listings + count + price histogram
      const [results, count, priceHistogram] = await Promise.all([
        fetchListings(params),
        fetchResultCount(params),
        fetchPriceHistogram(params),
      ]);

      return { results, count, priceHistogram, hasMore: count > 20 };
    },

    loadMoreResults: async (context) => {
      const params = buildSearchParams(context);
      params.page = context.pagination.page + 1;

      const results = await fetchListings(params);
      return { results, hasMore: results.length === 20 };
    },
  },
});

// URL synchronization hook
function useSearchUrlSync(state: SearchContext, send: (event: SearchEvent) => void) {
  const router = useRouter();

  // Sync URL to state on mount
  useEffect(() => {
    const params = parseSearchParams(router.query);

    if (params.query) send({ type: 'SET_QUERY', query: params.query });
    if (params.location) send({ type: 'SET_LOCATION', location: params.location });
    if (params.dates) send({ type: 'SET_DATES', dates: params.dates });
    // ... more params
  }, []);

  // Sync state to URL on change (debounced)
  useEffect(() => {
    const timeoutId = setTimeout(() => {
      const url = buildSearchUrl(state);
      router.replace(url, undefined, { shallow: true });
    }, 100);

    return () => clearTimeout(timeoutId);
  }, [state.query, state.location, state.dates, state.filters]);
}

Filter UI with Optimized Rendering

// Virtualized filter options for large lists
import { FixedSizeList } from 'react-window';

interface AmenityFilterProps {
  amenities: Amenity[];
  selected: Set<string>;
  onToggle: (id: string) => void;
}

export function AmenityFilter({
  amenities,
  selected,
  onToggle
}: AmenityFilterProps) {
  const [searchTerm, setSearchTerm] = useState('');

  // Filter and sort amenities
  const filteredAmenities = useMemo(() => {
    const filtered = amenities.filter(a =>
      a.name.toLowerCase().includes(searchTerm.toLowerCase())
    );

    // Selected items first, then alphabetical
    return filtered.sort((a, b) => {
      const aSelected = selected.has(a.id);
      const bSelected = selected.has(b.id);

      if (aSelected && !bSelected) return -1;
      if (!aSelected && bSelected) return 1;
      return a.name.localeCompare(b.name);
    });
  }, [amenities, selected, searchTerm]);

  // Optimistic toggle with rollback
  const handleToggle = useCallback((id: string) => {
    // Immediate UI update
    startTransition(() => {
      onToggle(id);
    });
  }, [onToggle]);

  // Row renderer for virtualization
  const Row = useCallback(({ index, style }: { index: number; style: React.CSSProperties }) => {
    const amenity = filteredAmenities[index];
    const isSelected = selected.has(amenity.id);

    return (
      <label
        key={amenity.id}
        style={style}
        className={`amenity-option ${isSelected ? 'selected' : ''}`}
      >
        <input
          type="checkbox"
          checked={isSelected}
          onChange={() => handleToggle(amenity.id)}
        />
        <span className="amenity-icon">{amenity.icon}</span>
        <span className="amenity-name">{amenity.name}</span>
        {amenity.count && (
          <span className="amenity-count">({amenity.count})</span>
        )}
      </label>
    );
  }, [filteredAmenities, selected, handleToggle]);

  return (
    <div className="amenity-filter">
      <input
        type="search"
        placeholder="Search amenities..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        className="amenity-search"
      />

      {filteredAmenities.length > 10 ? (
        // Virtualize for large lists
        <FixedSizeList
          height={400}
          itemCount={filteredAmenities.length}
          itemSize={48}
          width="100%"
        >
          {Row}
        </FixedSizeList>
      ) : (
        // Direct render for small lists
        <div className="amenity-list">
          {filteredAmenities.map((amenity, index) => (
            <Row key={amenity.id} index={index} style={{}} />
          ))}
        </div>
      )}
    </div>
  );
}

9. Calendar & Date Picker Performance

The date picker is one of Airbnb's most complex interactive components—it must show availability, pricing variations, and blocked dates while remaining responsive:

// Performance-optimized calendar with availability
import { memo, useMemo, useCallback } from 'react';

interface CalendarProps {
  availability: Map<string, AvailabilityDay>;
  selectedRange: { start: Date | null; end: Date | null };
  onSelect: (date: Date) => void;
  minStay: number;
  maxStay: number;
}

// Memoized day component to prevent re-renders
const CalendarDay = memo(function CalendarDay({
  date,
  availability,
  isSelected,
  isInRange,
  isCheckIn,
  isCheckOut,
  isDisabled,
  priceVariation,
  onClick,
}: CalendarDayProps) {
  const handleClick = useCallback(() => {
    if (!isDisabled) {
      onClick(date);
    }
  }, [date, isDisabled, onClick]);

  const className = useMemo(() => {
    const classes = ['calendar-day'];
    if (isSelected) classes.push('selected');
    if (isInRange) classes.push('in-range');
    if (isCheckIn) classes.push('check-in');
    if (isCheckOut) classes.push('check-out');
    if (isDisabled) classes.push('disabled');
    if (availability?.isWeekend) classes.push('weekend');
    return classes.join(' ');
  }, [isSelected, isInRange, isCheckIn, isCheckOut, isDisabled, availability]);

  return (
    <button
      type="button"
      className={className}
      onClick={handleClick}
      disabled={isDisabled}
      aria-label={formatDateLabel(date, availability)}
    >
      <span className="day-number">{date.getDate()}</span>
      {priceVariation && (
        <span className={`price-indicator ${priceVariation}`}>
          {priceVariation === 'high' ? '↑' : priceVariation === 'low' ? '↓' : ''}
        </span>
      )}
    </button>
  );
});

export function Calendar({
  availability,
  selectedRange,
  onSelect,
  minStay,
  maxStay,
}: CalendarProps) {
  // Pre-compute 12 months of calendar data
  const calendarMonths = useMemo(() => {
    const months: CalendarMonth[] = [];
    const today = new Date();

    for (let i = 0; i < 12; i++) {
      const monthDate = addMonths(today, i);
      const days = getDaysInMonth(monthDate);

      months.push({
        date: monthDate,
        days: days.map(day => {
          const key = formatDateKey(day);
          const dayAvailability = availability.get(key);

          return {
            date: day,
            availability: dayAvailability,
            isDisabled: !dayAvailability?.isAvailable || isPast(day),
            priceVariation: dayAvailability?.priceVariation,
          };
        }),
      });
    }

    return months;
  }, [availability]);

  // Compute selection state for all days
  const selectionState = useMemo(() => {
    const state = new Map<string, DaySelectionState>();

    if (selectedRange.start) {
      state.set(formatDateKey(selectedRange.start), {
        isSelected: true,
        isCheckIn: true
      });

      if (selectedRange.end) {
        state.set(formatDateKey(selectedRange.end), {
          isSelected: true,
          isCheckOut: true
        });

        // Mark in-range days
        let current = addDays(selectedRange.start, 1);
        while (current < selectedRange.end) {
          state.set(formatDateKey(current), { isInRange: true });
          current = addDays(current, 1);
        }
      }
    }

    return state;
  }, [selectedRange]);

  // Virtualized month rendering
  const renderMonth = useCallback((month: CalendarMonth, index: number) => {
    return (
      <div key={month.date.toISOString()} className="calendar-month">
        <h3 className="month-header">
          {format(month.date, 'MMMM yyyy')}
        </h3>
        <div className="calendar-grid" role="grid">
          {/* Day headers */}
          <div className="day-headers">
            {['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map(day => (
              <div key={day} className="day-header">{day}</div>
            ))}
          </div>

          {/* Day cells */}
          <div className="days-grid">
            {month.days.map(day => {
              const key = formatDateKey(day.date);
              const state = selectionState.get(key) || {};

              return (
                <CalendarDay
                  key={key}
                  date={day.date}
                  availability={day.availability}
                  isSelected={state.isSelected || false}
                  isInRange={state.isInRange || false}
                  isCheckIn={state.isCheckIn || false}
                  isCheckOut={state.isCheckOut || false}
                  isDisabled={day.isDisabled}
                  priceVariation={day.priceVariation}
                  onClick={onSelect}
                />
              );
            })}
          </div>
        </div>
      </div>
    );
  }, [selectionState, onSelect]);

  return (
    <div className="calendar-container">
      {/* Intersection Observer for lazy loading months */}
      <IntersectionObserverList
        items={calendarMonths}
        renderItem={renderMonth}
        rootMargin="200px"
        className="months-container"
      />
    </div>
  );
}

// Efficient intersection observer for lazy rendering
function IntersectionObserverList<T>({
  items,
  renderItem,
  rootMargin,
  className,
}: {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  rootMargin: string;
  className?: string;
}) {
  const [visibleIndices, setVisibleIndices] = useState<Set<number>>(
    () => new Set([0, 1, 2]) // Initially render first 3
  );

  const observerRef = useRef<IntersectionObserver | null>(null);
  const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map());

  useEffect(() => {
    observerRef.current = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          const index = parseInt(entry.target.getAttribute('data-index')!, 10);

          setVisibleIndices(prev => {
            const next = new Set(prev);
            if (entry.isIntersecting) {
              next.add(index);
              // Also add adjacent months for smooth scrolling
              if (index > 0) next.add(index - 1);
              if (index < items.length - 1) next.add(index + 1);
            }
            return next;
          });
        });
      },
      { rootMargin }
    );

    // Observe all item containers
    itemRefs.current.forEach((el) => {
      observerRef.current?.observe(el);
    });

    return () => observerRef.current?.disconnect();
  }, [items.length, rootMargin]);

  return (
    <div className={className}>
      {items.map((item, index) => (
        <div
          key={index}
          ref={(el) => el && itemRefs.current.set(index, el)}
          data-index={index}
          style={{ minHeight: visibleIndices.has(index) ? undefined : '300px' }}
        >
          {visibleIndices.has(index) ? renderItem(item, index) : null}
        </div>
      ))}
    </div>
  );
}

10. Data Fetching & Caching Strategy

Airbnb uses a combination of Apollo Client for GraphQL and custom caching for critical paths:

// Multi-layer caching architecture
import { ApolloClient, InMemoryCache, ApolloLink } from '@apollo/client';

// Cache configuration with custom merge functions
const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        // Search results: replace on new search, merge on pagination
        searchListings: {
          keyArgs: ['query', 'location', 'dates', 'guests', 'filters'],
          merge(existing, incoming, { args }) {
            if (args?.page === 1 || !existing) {
              return incoming;
            }
            return {
              ...incoming,
              listings: [...existing.listings, ...incoming.listings],
            };
          },
        },

        // Single listing: always cache by ID
        listing: {
          read(_, { args, toReference }) {
            return toReference({ __typename: 'Listing', id: args?.id });
          },
        },
      },
    },

    Listing: {
      fields: {
        // Price can change based on dates - don't cache aggressively
        pricing: {
          merge: false,
        },

        // Availability changes frequently
        availability: {
          merge(existing, incoming) {
            // Only update if newer
            if (!existing || incoming.updatedAt > existing.updatedAt) {
              return incoming;
            }
            return existing;
          },
        },

        // Reviews rarely change - cache aggressively
        reviews: {
          keyArgs: ['listingId'],
          merge(existing, incoming, { args }) {
            if (args?.offset === 0) return incoming;
            return {
              ...incoming,
              items: [...(existing?.items || []), ...incoming.items],
            };
          },
        },
      },
    },
  },
});

// Custom link for request deduplication
const dedupeLink = new ApolloLink((operation, forward) => {
  const key = operation.operationName + JSON.stringify(operation.variables);

  // Check if identical request is in flight
  if (inflightRequests.has(key)) {
    return inflightRequests.get(key)!;
  }

  const observable = forward(operation);
  inflightRequests.set(key, observable);

  // Clean up after response
  observable.subscribe({
    complete: () => inflightRequests.delete(key),
    error: () => inflightRequests.delete(key),
  });

  return observable;
});

// Prefetch critical data based on user behavior
class PrefetchManager {
  private prefetchQueue: Set<string> = new Set();
  private prefetchedIds: Set<string> = new Set();

  // Prefetch listing on hover
  prefetchListing(listingId: string) {
    if (this.prefetchedIds.has(listingId)) return;

    this.prefetchQueue.add(listingId);
    this.schedulePrefetch();
  }

  private schedulePrefetch = debounce(() => {
    const ids = Array.from(this.prefetchQueue);
    this.prefetchQueue.clear();

    // Batch prefetch multiple listings
    requestIdleCallback(() => {
      apolloClient.query({
        query: LISTING_PREVIEW_QUERY,
        variables: { ids },
        fetchPolicy: 'cache-first',
      });

      ids.forEach(id => this.prefetchedIds.add(id));
    });
  }, 100);

  // Prefetch search results based on likely navigation
  prefetchSearchVariation(baseParams: SearchParams, variations: Partial<SearchParams>[]) {
    requestIdleCallback(() => {
      variations.forEach(variation => {
        const params = { ...baseParams, ...variation };

        apolloClient.query({
          query: SEARCH_LISTINGS_QUERY,
          variables: params,
          fetchPolicy: 'cache-first',
        });
      });
    });
  }
}

// Usage in listing card
function ListingCard({ listing }: { listing: Listing }) {
  const prefetchManager = usePrefetchManager();

  const handleMouseEnter = useCallback(() => {
    prefetchManager.prefetchListing(listing.id);
  }, [listing.id, prefetchManager]);

  return (
    <article
      className="listing-card"
      onMouseEnter={handleMouseEnter}
    >
      {/* ... */}
    </article>
  );
}

11. Performance Monitoring & RUM

Airbnb has comprehensive Real User Monitoring to track Core Web Vitals in production:

// Performance monitoring system
import { onLCP, onINP, onCLS, onFCP, onTTFB } from 'web-vitals';

interface PerformanceMetric {
  name: string;
  value: number;
  rating: 'good' | 'needs-improvement' | 'poor';
  attribution?: Record<string, unknown>;
}

class PerformanceMonitor {
  private metrics: PerformanceMetric[] = [];
  private sessionId: string;
  private pageId: string;

  constructor() {
    this.sessionId = crypto.randomUUID();
    this.pageId = crypto.randomUUID();

    this.initWebVitals();
    this.initCustomMetrics();
    this.initLongTaskObserver();
  }

  private initWebVitals() {
    // Largest Contentful Paint
    onLCP((metric) => {
      this.report({
        name: 'LCP',
        value: metric.value,
        rating: metric.rating,
        attribution: {
          element: metric.attribution?.element?.tagName,
          url: metric.attribution?.url,
          resourceLoadDelay: metric.attribution?.resourceLoadDelay,
          elementRenderDelay: metric.attribution?.elementRenderDelay,
        },
      });
    }, { reportAllChanges: true });

    // Interaction to Next Paint
    onINP((metric) => {
      this.report({
        name: 'INP',
        value: metric.value,
        rating: metric.rating,
        attribution: {
          eventType: metric.attribution?.eventType,
          eventTarget: metric.attribution?.eventTarget,
          interactionTarget: metric.attribution?.interactionTarget,
          loadState: metric.attribution?.loadState,
        },
      });
    });

    // Cumulative Layout Shift
    onCLS((metric) => {
      this.report({
        name: 'CLS',
        value: metric.value,
        rating: metric.rating,
        attribution: {
          largestShiftTarget: metric.attribution?.largestShiftTarget,
          largestShiftValue: metric.attribution?.largestShiftValue,
          largestShiftTime: metric.attribution?.largestShiftTime,
        },
      });
    });

    // First Contentful Paint
    onFCP((metric) => {
      this.report({
        name: 'FCP',
        value: metric.value,
        rating: metric.rating,
      });
    });

    // Time to First Byte
    onTTFB((metric) => {
      this.report({
        name: 'TTFB',
        value: metric.value,
        rating: metric.rating,
        attribution: {
          waitingDuration: metric.attribution?.waitingDuration,
          cacheDuration: metric.attribution?.cacheDuration,
          dnsDuration: metric.attribution?.dnsDuration,
          connectionDuration: metric.attribution?.connectionDuration,
          requestDuration: metric.attribution?.requestDuration,
        },
      });
    });
  }

  private initCustomMetrics() {
    // Track search result render time
    this.observeCustomMetric('search-results-render', () => {
      const searchStart = performance.getEntriesByName('search-start')[0];
      const searchEnd = performance.getEntriesByName('search-results-visible')[0];

      if (searchStart && searchEnd) {
        return searchEnd.startTime - searchStart.startTime;
      }
      return null;
    });

    // Track image load completion
    this.observeCustomMetric('above-fold-images-loaded', () => {
      const images = document.querySelectorAll('img[data-priority="high"]');
      const allLoaded = Array.from(images).every(
        img => (img as HTMLImageElement).complete
      );

      if (allLoaded) {
        return performance.now();
      }
      return null;
    });

    // Track map interactive
    this.observeCustomMetric('map-interactive', () => {
      const mapReady = performance.getEntriesByName('map-interactive')[0];
      return mapReady?.startTime || null;
    });
  }

  private initLongTaskObserver() {
    if (!('PerformanceObserver' in window)) return;

    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.duration > 50) {
          this.report({
            name: 'long-task',
            value: entry.duration,
            rating: entry.duration > 100 ? 'poor' : 'needs-improvement',
            attribution: {
              startTime: entry.startTime,
              attribution: (entry as any).attribution,
            },
          });
        }
      }
    });

    observer.observe({ type: 'longtask', buffered: true });
  }

  private observeCustomMetric(
    name: string,
    getValue: () => number | null
  ) {
    const checkValue = () => {
      const value = getValue();
      if (value !== null) {
        this.report({
          name,
          value,
          rating: this.rateCustomMetric(name, value),
        });
        return true;
      }
      return false;
    };

    // Check immediately
    if (checkValue()) return;

    // Poll until value available
    const interval = setInterval(() => {
      if (checkValue()) {
        clearInterval(interval);
      }
    }, 100);

    // Stop after 30 seconds
    setTimeout(() => clearInterval(interval), 30000);
  }

  private rateCustomMetric(
    name: string,
    value: number
  ): 'good' | 'needs-improvement' | 'poor' {
    const thresholds: Record<string, [number, number]> = {
      'search-results-render': [1000, 2500],
      'above-fold-images-loaded': [2000, 4000],
      'map-interactive': [3000, 5000],
    };

    const [good, poor] = thresholds[name] || [1000, 3000];

    if (value <= good) return 'good';
    if (value <= poor) return 'needs-improvement';
    return 'poor';
  }

  private report(metric: PerformanceMetric) {
    this.metrics.push(metric);

    // Send to analytics
    navigator.sendBeacon('/api/analytics/performance', JSON.stringify({
      sessionId: this.sessionId,
      pageId: this.pageId,
      url: window.location.href,
      userAgent: navigator.userAgent,
      connection: (navigator as any).connection?.effectiveType,
      deviceMemory: (navigator as any).deviceMemory,
      ...metric,
      timestamp: Date.now(),
    }));
  }
}

// Initialize on page load
if (typeof window !== 'undefined') {
  new PerformanceMonitor();
}

12. Bundle Optimization

Airbnb uses aggressive code splitting and tree shaking to minimize JavaScript payload:

// Webpack configuration for optimal bundling
import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';

const config: webpack.Configuration = {
  optimization: {
    // Split chunks for optimal caching
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: 25,
      minSize: 20000,

      cacheGroups: {
        // Core framework (rarely changes)
        framework: {
          test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
          name: 'framework',
          priority: 40,
          enforce: true,
        },

        // Design system (changes with releases)
        designSystem: {
          test: /[\\/]node_modules[\\/]@airbnb[\\/]dls[\\/]/,
          name: 'design-system',
          priority: 30,
        },

        // Vendor libraries (grouped by update frequency)
        vendorHigh: {
          test: /[\\/]node_modules[\\/](lodash|moment|date-fns)[\\/]/,
          name: 'vendor-utils',
          priority: 20,
        },

        // Map libraries (only loaded on search page)
        maps: {
          test: /[\\/]node_modules[\\/](mapbox-gl|supercluster)[\\/]/,
          name: 'maps',
          priority: 20,
          chunks: 'async',
        },

        // Default vendor chunk
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
        },

        // Shared components across pages
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true,
        },
      },
    },

    // Module IDs for long-term caching
    moduleIds: 'deterministic',

    // Runtime chunk for better caching
    runtimeChunk: 'single',
  },

  plugins: [
    // Analyze bundle in CI
    process.env.ANALYZE && new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: 'bundle-report.html',
    }),

    // Define feature flags for tree shaking
    new webpack.DefinePlugin({
      'process.env.FEATURE_NEW_SEARCH': JSON.stringify(
        process.env.FEATURE_NEW_SEARCH === 'true'
      ),
    }),
  ].filter(Boolean),
};

// Route-based code splitting
const routes = {
  '/': () => import(/* webpackChunkName: "home" */ './pages/Home'),
  '/s/:location': () => import(/* webpackChunkName: "search" */ './pages/Search'),
  '/rooms/:id': () => import(/* webpackChunkName: "listing" */ './pages/Listing'),
  '/book/:id': () => import(/* webpackChunkName: "booking" */ './pages/Booking'),
  '/hosting': () => import(/* webpackChunkName: "hosting" */ './pages/Hosting'),
  '/account': () => import(/* webpackChunkName: "account" */ './pages/Account'),
};

// Prefetch likely next routes
function usePrefetchRoutes(currentRoute: string) {
  useEffect(() => {
    const prefetchMap: Record<string, string[]> = {
      '/': ['/s/:location'], // Home → Search
      '/s/:location': ['/rooms/:id'], // Search → Listing
      '/rooms/:id': ['/book/:id'], // Listing → Booking
    };

    const toPrefetch = prefetchMap[currentRoute] || [];

    // Prefetch after idle
    requestIdleCallback(() => {
      toPrefetch.forEach(route => {
        const loader = routes[route];
        if (loader) {
          loader(); // Triggers chunk prefetch
        }
      });
    });
  }, [currentRoute]);
}

13. Third-Party Script Management

Third-party scripts (analytics, A/B testing, ads) can devastate Core Web Vitals. Airbnb uses a strict loading strategy:

// Third-party script manager with performance controls
type ScriptPriority = 'critical' | 'high' | 'medium' | 'low' | 'idle';

interface ThirdPartyScript {
  id: string;
  src: string;
  priority: ScriptPriority;
  loadCondition?: () => boolean;
  onLoad?: () => void;
  timeout?: number;
}

class ThirdPartyScriptManager {
  private scripts: Map<string, ThirdPartyScript> = new Map();
  private loadedScripts: Set<string> = new Set();
  private failedScripts: Set<string> = new Set();

  register(script: ThirdPartyScript) {
    this.scripts.set(script.id, script);
  }

  async loadAll() {
    // Group by priority
    const byPriority = new Map<ScriptPriority, ThirdPartyScript[]>();

    this.scripts.forEach(script => {
      const list = byPriority.get(script.priority) || [];
      list.push(script);
      byPriority.set(script.priority, list);
    });

    // Load in priority order
    const priorities: ScriptPriority[] = ['critical', 'high', 'medium', 'low', 'idle'];

    for (const priority of priorities) {
      const scripts = byPriority.get(priority) || [];

      if (priority === 'critical') {
        // Critical scripts: load immediately, block render if needed
        await Promise.all(scripts.map(s => this.loadScript(s)));
      } else if (priority === 'high') {
        // High priority: load after FCP
        await this.waitForFCP();
        await Promise.all(scripts.map(s => this.loadScript(s)));
      } else if (priority === 'medium') {
        // Medium: load after LCP
        await this.waitForLCP();
        scripts.forEach(s => this.loadScript(s));
      } else if (priority === 'low') {
        // Low: load on idle
        requestIdleCallback(() => {
          scripts.forEach(s => this.loadScript(s));
        });
      } else {
        // Idle: load after everything else
        setTimeout(() => {
          requestIdleCallback(() => {
            scripts.forEach(s => this.loadScript(s));
          });
        }, 5000);
      }
    }
  }

  private async loadScript(script: ThirdPartyScript): Promise<void> {
    if (this.loadedScripts.has(script.id)) return;
    if (script.loadCondition && !script.loadCondition()) return;

    return new Promise((resolve, reject) => {
      const el = document.createElement('script');
      el.src = script.src;
      el.async = true;

      // Add resource hints
      el.setAttribute('importance', this.getImportance(script.priority));

      const timeout = script.timeout || 10000;
      const timeoutId = setTimeout(() => {
        this.failedScripts.add(script.id);
        reject(new Error(`Script ${script.id} timed out`));
      }, timeout);

      el.onload = () => {
        clearTimeout(timeoutId);
        this.loadedScripts.add(script.id);
        script.onLoad?.();
        resolve();
      };

      el.onerror = () => {
        clearTimeout(timeoutId);
        this.failedScripts.add(script.id);
        reject(new Error(`Script ${script.id} failed to load`));
      };

      document.head.appendChild(el);
    });
  }

  private getImportance(priority: ScriptPriority): string {
    switch (priority) {
      case 'critical': return 'high';
      case 'high': return 'high';
      case 'medium': return 'auto';
      default: return 'low';
    }
  }

  private waitForFCP(): Promise<void> {
    return new Promise(resolve => {
      if (performance.getEntriesByName('first-contentful-paint').length > 0) {
        resolve();
        return;
      }

      const observer = new PerformanceObserver((list) => {
        if (list.getEntriesByName('first-contentful-paint').length > 0) {
          observer.disconnect();
          resolve();
        }
      });

      observer.observe({ type: 'paint', buffered: true });
    });
  }

  private waitForLCP(): Promise<void> {
    return new Promise(resolve => {
      const observer = new PerformanceObserver((list) => {
        // LCP is the last entry
        const entries = list.getEntries();
        if (entries.length > 0) {
          // Wait a bit more to ensure LCP is final
          setTimeout(() => {
            observer.disconnect();
            resolve();
          }, 500);
        }
      });

      observer.observe({ type: 'largest-contentful-paint', buffered: true });

      // Fallback timeout
      setTimeout(resolve, 5000);
    });
  }
}

// Script registration
const scriptManager = new ThirdPartyScriptManager();

// Critical: consent management (GDPR)
scriptManager.register({
  id: 'consent-manager',
  src: '/scripts/consent.js',
  priority: 'critical',
});

// High: analytics (after consent)
scriptManager.register({
  id: 'analytics',
  src: 'https://analytics.airbnb.com/a.js',
  priority: 'high',
  loadCondition: () => hasConsent('analytics'),
});

// Medium: A/B testing
scriptManager.register({
  id: 'experiments',
  src: '/scripts/experiments.js',
  priority: 'medium',
});

// Low: customer support chat
scriptManager.register({
  id: 'support-chat',
  src: 'https://support.airbnb.com/chat.js',
  priority: 'low',
  loadCondition: () => !isGuestCheckout(),
});

// Idle: marketing pixels
scriptManager.register({
  id: 'marketing',
  src: '/scripts/marketing.js',
  priority: 'idle',
  loadCondition: () => hasConsent('marketing'),
});

// Initialize after DOM ready
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', () => scriptManager.loadAll());
} else {
  scriptManager.loadAll();
}

14. Architecture Evolution & Lessons Learned

Evolution Timeline

┌─────────────────────────────────────────────────────────────────────────────┐
│                    AIRBNB FRONTEND ARCHITECTURE EVOLUTION                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  2014      2016        2018        2020        2022        2024            │
│    │         │           │           │           │           │             │
│    ▼         ▼           ▼           ▼           ▼           ▼             │
│  ┌─────┐  ┌─────┐     ┌─────┐     ┌─────┐     ┌─────┐     ┌─────┐         │
│  │Rails│  │React│     │Hyper│     │Apollo│    │ RSC │     │Niobe│         │
│  │ERB  │  │ +   │     │nova │     │ +    │    │Adopt│     │ +   │         │
│  │     │  │Flux │     │SSR  │     │Perf  │    │tion │     │Edge │         │
│  └─────┘  └─────┘     └─────┘     └─────┘     └─────┘     └─────┘         │
│    │         │           │           │           │           │             │
│    │         │           │           │           │           │             │
│  Full      Client       Server      GraphQL     Server      Edge          │
│  page      render       render      adoption    Components  rendering     │
│  reload    + API        + hydrate   + caching   + streaming + AI          │
│                                                                             │
│  METRICS EVOLUTION:                                                         │
│  ─────────────────                                                         │
│  LCP:  8s → 4s → 2.5s → 2.0s → 1.5s → 1.2s                                │
│  INP:  N/A → 500ms → 300ms → 200ms → 150ms → 100ms                        │
│  CLS:  N/A → 0.5 → 0.25 → 0.15 → 0.08 → 0.05                              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Key Lessons

1. Image optimization is never "done"

// Evolution of image strategy
const imageStrategyEvolution = {
  2016: {
    format: 'jpg',
    sizes: [1, 2], // 1x, 2x only
    lazy: false,
    placeholder: 'none',
  },
  2018: {
    format: 'webp-with-fallback',
    sizes: [1, 2, 3],
    lazy: 'native',
    placeholder: 'blur-up',
  },
  2020: {
    format: 'avif-webp-fallback',
    sizes: [320, 640, 960, 1280, 1920],
    lazy: 'intersection-observer',
    placeholder: 'blurhash',
  },
  2024: {
    format: 'avif-first',
    sizes: 'responsive-hints',
    lazy: 'priority-based',
    placeholder: 'blurhash-with-dominant-color',
    fetchPriority: 'explicit',
  },
};

2. Hydration is the enemy of INP

// Before: Full page hydration blocked interactions
// 2018 approach
hydrateRoot(document, <App />); // 500ms+ blocking

// After: Selective, prioritized hydration
// 2024 approach
const scheduler = new HydrationScheduler();
scheduler.schedule({ priority: 'critical', component: SearchBar });
scheduler.schedule({ priority: 'high', component: ListingCards });
scheduler.schedule({ priority: 'idle', component: Footer });
// INP improved from 350ms → 120ms

3. Third-party scripts require constant vigilance

// Tracking impact of third parties over time
const thirdPartyImpact = {
  2019: {
    scripts: 12,
    totalSize: '450KB',
    mainThreadTime: '1.2s',
    lcpImpact: '+800ms',
  },
  2024: {
    scripts: 8, // Consolidated and removed
    totalSize: '180KB', // Optimized bundles
    mainThreadTime: '400ms', // Deferred loading
    lcpImpact: '+150ms', // Loaded after LCP
  },
};

15. Tradeoffs & Anti-Patterns Avoided

Tradeoff: Static Generation vs. Dynamic Pricing

// Challenge: Listing pages could be statically generated for speed,
// but prices change based on dates, availability, and dynamic pricing

// Anti-pattern: Full static generation with client-side price fetch
// Problem: Price "pops in" after load, causes CLS, confuses users

// Solution: Hybrid approach with streaming
async function getStaticProps({ params }) {
  // Static: content that rarely changes
  const listing = await getListingContent(params.id);

  return {
    props: { listing },
    revalidate: 3600, // Revalidate hourly
  };
}

function ListingPage({ listing }) {
  return (
    <>
      {/* Static content renders immediately */}
      <ListingGallery photos={listing.photos} />
      <ListingDescription listing={listing} />

      {/* Price streams in with Suspense */}
      <Suspense fallback={<PriceSkeleton />}>
        <DynamicPricing listingId={listing.id} />
      </Suspense>
    </>
  );
}

// Server component for dynamic pricing
async function DynamicPricing({ listingId }) {
  const pricing = await fetchDynamicPricing(listingId);
  return <PriceDisplay pricing={pricing} />;
}

Tradeoff: Bundle Size vs. Development Velocity

// Challenge: Design Language System (DLS) is comprehensive but large
// Full DLS: 180KB gzipped
// Used components per page: ~20KB worth

// Anti-pattern: Import entire DLS
import { Button, Card, Modal, ... } from '@airbnb/dls'; // 180KB

// Solution: Tree-shakeable exports with component-level splitting
// webpack.config.js
{
  resolve: {
    alias: {
      '@airbnb/dls': '@airbnb/dls/es', // ES modules for tree shaking
    },
  },
}

// Component imports are now tree-shakeable
import { Button } from '@airbnb/dls/es/Button'; // 3KB
import { Card } from '@airbnb/dls/es/Card'; // 5KB

// Result: 180KB → 25KB average per page

Anti-Pattern Avoided: Over-Fetching GraphQL

// Anti-pattern: One giant query for all page data
const LISTING_PAGE_QUERY = gql`
  query ListingPage($id: ID!) {
    listing(id: $id) {
      # 50+ fields including nested objects
      # Most fields not needed above the fold
    }
  }
`;

// Solution: Split queries by render priority
const LISTING_CRITICAL_QUERY = gql`
  query ListingCritical($id: ID!) {
    listing(id: $id) {
      id
      title
      photos(first: 5) { url blurHash }
      pricing { basePrice currency }
      location { city country }
    }
  }
`;

const LISTING_DEFERRED_QUERY = gql`
  query ListingDeferred($id: ID!) {
    listing(id: $id) {
      description
      amenities { id name icon }
      host { id name photo superhost }
      reviews(first: 10) { id rating comment author { name } }
      # ... more fields
    }
  }
`;

// Load critical first, deferred after LCP
function ListingPage({ id }) {
  const { data: critical } = useQuery(LISTING_CRITICAL_QUERY, { variables: { id } });
  const { data: deferred } = useQuery(LISTING_DEFERRED_QUERY, {
    variables: { id },
    skip: !critical, // Wait for critical
    fetchPolicy: 'cache-first',
  });

  // ...
}

16. Future Architecture Direction

Speculative Prefetching with ML

// ML-powered prefetch predictions
interface PrefetchPrediction {
  listingId: string;
  probability: number;
  predictedAction: 'view' | 'book' | 'save';
}

class MLPrefetchManager {
  private model: PredictionModel;

  async predictNextActions(context: UserContext): Promise<PrefetchPrediction[]> {
    const features = this.extractFeatures(context);
    const predictions = await this.model.predict(features);

    // Only prefetch high-confidence predictions
    return predictions
      .filter(p => p.probability > 0.7)
      .slice(0, 5); // Max 5 prefetches
  }

  private extractFeatures(context: UserContext): ModelFeatures {
    return {
      // User behavior signals
      scrollVelocity: context.scrollVelocity,
      hoverDuration: context.lastHoverDuration,
      searchRefinements: context.filterChanges,

      // Content signals
      priceRange: context.viewedPriceRange,
      propertyTypes: context.viewedPropertyTypes,

      // Temporal signals
      dayOfWeek: new Date().getDay(),
      hourOfDay: new Date().getHours(),
      daysUntilTrip: context.searchDates?.daysUntil,
    };
  }

  async prefetchPredicted(predictions: PrefetchPrediction[]) {
    for (const prediction of predictions) {
      if (prediction.predictedAction === 'view') {
        // Prefetch listing page
        prefetchPage(`/rooms/${prediction.listingId}`);
      } else if (prediction.predictedAction === 'book') {
        // Prefetch listing + booking flow
        prefetchPage(`/rooms/${prediction.listingId}`);
        prefetchPage(`/book/${prediction.listingId}`);
      }
    }
  }
}

Edge-First Architecture

// Edge rendering for global performance
// Deployed to 200+ edge locations

export const config = {
  runtime: 'edge',
  regions: ['iad1', 'sfo1', 'cdg1', 'hnd1', 'syd1'], // Global
};

export default async function handler(request: Request) {
  const url = new URL(request.url);
  const geo = request.geo; // Edge-provided geolocation

  // Localize content at edge
  const locale = detectLocale(request.headers, geo);
  const currency = getCurrencyForRegion(geo.country);

  // Fetch from nearest origin
  const origin = selectNearestOrigin(geo);
  const data = await fetchFromOrigin(origin, url.pathname);

  // Render at edge with localization
  const html = await renderToString(
    <App
      data={data}
      locale={locale}
      currency={currency}
    />
  );

  return new Response(html, {
    headers: {
      'Content-Type': 'text/html',
      'Cache-Control': 'public, max-age=60, stale-while-revalidate=600',
      'Vary': 'Accept-Language, Accept-Encoding',
    },
  });
}

Conclusion

Airbnb's frontend architecture represents a masterclass in balancing visual richness with performance. Key takeaways:

  1. Images dominate performance - BlurHash placeholders, AVIF/WebP, responsive srcset, and fetchpriority are non-negotiable.

  2. Hydration must be strategic - Full-page hydration is an anti-pattern. Priority-based selective hydration keeps INP under 200ms.

  3. Maps require special handling - Lazy load, use static placeholders, cluster markers, and defer interactivity.

  4. CLS prevention requires engineering discipline - Aspect ratios, skeleton screens, and contain CSS are essential.

  5. Third-party scripts need governance - Strict priority ordering and post-LCP loading for non-critical scripts.

  6. Performance is a feature, not a metric - Every 100ms of LCP improvement directly correlates with booking conversion.

The architecture continues to evolve with edge rendering, ML-powered prefetching, and React Server Components, but the core principle remains: deliver the visual trust signals users need to book a stranger's home, faster than they expect.


Performance targets achieved (p75):

  • LCP: 1.8s (target: 2.5s) ✓
  • INP: 120ms (target: 200ms) ✓
  • CLS: 0.05 (target: 0.1) ✓
  • TTFB: 180ms (target: 800ms) ✓

What did you think?

© 2026 Vidhya Sagar Thakur. All rights reserved.