> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fanfare.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Product launch

# Product Launch Use Case

Learn how to use Fanfare to manage high-demand product launches with virtual queues and controlled access.

## Overview

Product launches often generate traffic spikes that can overwhelm your infrastructure. Fanfare helps you manage demand by creating a fair, orderly queue that protects your site while delivering a great customer experience.

**What you'll learn:**

* Planning your product launch strategy
* Configuring a queue-based experience
* Building the launch landing page
* Managing the launch day workflow
* Analyzing post-launch metrics

**Complexity:** Intermediate
**Time to complete:** 45 minutes

## Prerequisites

* Fanfare account with queue feature enabled
* Product page ready for launch
* Understanding of expected traffic levels
* Integration with your e-commerce platform

## When to Use Queues for Product Launches

| Scenario                                 | Recommended Approach       |
| ---------------------------------------- | -------------------------- |
| Expected demand > 2x available inventory | Queue recommended          |
| Traffic spike > 10x normal               | Queue strongly recommended |
| High-value items with limited stock      | Queue with authentication  |
| Fair access is brand priority            | Queue essential            |
| First-come-first-served required         | Queue required             |

## Step 1: Plan Your Launch

### Define Launch Parameters

```typescript theme={null}
// Example launch configuration
const launchConfig = {
  // Timing
  scheduledStart: new Date("2024-06-15T10:00:00Z"),
  preQueueStart: new Date("2024-06-15T09:30:00Z"), // 30 min early access
  admissionWindowMinutes: 15,

  // Capacity
  totalInventory: 1000,
  concurrentAdmissions: 50, // Users shopping at once
  admissionRatePerMinute: 100,

  // Experience
  showPosition: true,
  showEstimatedWait: true,
  allowRequeue: false,
};
```

### Calculate Queue Settings

```typescript theme={null}
function calculateQueueSettings(config: {
  totalInventory: number;
  expectedDemand: number;
  avgCheckoutTime: number;
  admissionWindow: number;
}) {
  const { totalInventory, expectedDemand, avgCheckoutTime, admissionWindow } = config;

  // How many users can complete checkout in the admission window
  const checkoutsPerWindow = admissionWindow / avgCheckoutTime;

  // Concurrent admissions to maintain flow
  const concurrentAdmissions = Math.ceil(totalInventory / checkoutsPerWindow);

  // Rate to process the expected demand
  const totalLaunchDuration = (expectedDemand / concurrentAdmissions) * avgCheckoutTime;
  const admissionRate = Math.ceil(expectedDemand / totalLaunchDuration);

  return {
    concurrentAdmissions,
    admissionRatePerMinute: admissionRate,
    estimatedLaunchDuration: totalLaunchDuration,
  };
}

// Example calculation
const settings = calculateQueueSettings({
  totalInventory: 1000,
  expectedDemand: 50000,
  avgCheckoutTime: 3, // minutes
  admissionWindow: 15, // minutes
});

console.log(settings);
// { concurrentAdmissions: 200, admissionRatePerMinute: 100, estimatedLaunchDuration: 500 }
```

## Step 2: Create the Queue Experience

### Admin API Configuration

```typescript theme={null}
async function createProductLaunchQueue() {
  const queue = await createFanfareExperience({
    name: "Summer Sneaker Drop 2024",
    slug: "summer-sneaker-drop-2024",

    // Scheduling
    scheduledStart: new Date("2024-06-15T10:00:00Z"),
    scheduledEnd: new Date("2024-06-15T18:00:00Z"),

    // Capacity settings
    config: {
      maxConcurrentAdmissions: 50,
      admissionWindowSeconds: 900, // 15 minutes
      admissionRatePerMinute: 100,

      // Queue behavior
      allowRequeue: false,
      requireAuthentication: false,
      fairnessMode: "strict", // First-come-first-served

      // Display settings
      showPosition: true,
      showEstimatedWait: true,
      showQueueLength: false, // Don't show total to avoid discouraging users
    },

    // Metadata for your system
    metadata: {
      productId: "sneaker-summer-2024",
      campaign: "summer-launch",
      inventoryCount: 1000,
    },

    // Branding
    branding: {
      primaryColor: "#FF5722",
      logoUrl: "https://your-brand.com/logo.png",
      backgroundImageUrl: "https://your-brand.com/launch-bg.jpg",
    },
  });

  return queue;
}
```

### Enable Pre-Queue (Waiting Room)

```typescript theme={null}
async function enablePreQueue(queueId: string) {
  await updateLaunchExperience(queueId, {
    config: {
      preQueueEnabled: true,
      preQueueStartTime: new Date("2024-06-15T09:30:00Z"),
      preQueueMessage: "The launch begins at 10:00 AM ET. Stay on this page to secure your spot.",
    },
  });
}
```

## Step 3: Build the Launch Page

### React Launch Page Component

```tsx theme={null}
// pages/launch/[slug].tsx
import { FanfareProvider } from "@fanfare-io/fanfare-sdk-react";
import { ProductLaunchExperience } from "@/components/ProductLaunchExperience";
import { ProductInfo } from "@/components/ProductInfo";
import { LaunchCountdown } from "@/components/LaunchCountdown";

interface LaunchPageProps {
  queueId: string;
  product: Product;
  launchTime: string;
}

export default function LaunchPage({ queueId, product, launchTime }: LaunchPageProps) {
  return (
    <FanfareProvider
      organizationId={process.env.NEXT_PUBLIC_FANFARE_ORG_ID!}
      publishableKey={process.env.NEXT_PUBLIC_FANFARE_PUBLISHABLE_KEY!}
    >
      <div className="launch-page">
        <header className="launch-header">
          <h1>{product.name}</h1>
          <LaunchCountdown targetTime={launchTime} />
        </header>

        <main className="launch-content">
          <div className="product-preview">
            <ProductInfo product={product} />
          </div>

          <div className="experience-container">
            <ProductLaunchExperience queueId={queueId} product={product} />
          </div>
        </main>
      </div>
    </FanfareProvider>
  );
}

export async function getServerSideProps({ params }: { params: { slug: string } }) {
  const launch = await getLaunchBySlug(params.slug);

  return {
    props: {
      queueId: launch.queueId,
      product: launch.product,
      launchTime: launch.scheduledStart,
    },
  };
}
```

### Launch Experience Component

```tsx theme={null}
// components/ProductLaunchExperience.tsx
import { useExperienceJourney } from "@fanfare-io/fanfare-sdk-react";
import type { SequenceView } from "@fanfare-io/fanfare-sdk-core/experiences";
import { useState, useEffect } from "react";

interface ProductLaunchExperienceProps {
  queueId: string;
  product: Product;
}

export function ProductLaunchExperience({ queueId, product }: ProductLaunchExperienceProps) {
  const { view, start } = useExperienceJourney(queueId, { autoStart: true });

  const stage = view?.journeyStage === "routed" ? view.sequence.phase : (view?.journeyStage ?? "loading");

  // Track analytics
  useEffect(() => {
    if (stage) {
      analytics.track("launch_stage_change", {
        queueId,
        productId: product.id,
        stage,
      });
    }
  }, [stage, queueId, product.id]);

  return (
    <div className="launch-experience">
      {view?.journeyStage === "ready" && <PreLaunchState onStart={start} />}

      {view?.journeyStage === "routing" && <EnteringState />}

      {view?.journeyStage === "routed" && view.sequence.phase === "participating" && (
        <QueueState sequence={view.sequence} product={product} />
      )}

      {view?.journeyStage === "routed" && view.sequence.phase === "granted" && (
        <AdmittedState sequence={view.sequence} product={product} />
      )}

      {view?.journeyStage === "routed" && view.sequence.phase === "ended" && <CompletedState />}

      {!view && <LoadingState message="Loading launch..." />}
    </div>
  );
}

function PreLaunchState({ onStart }: { onStart: () => Promise<unknown> }) {
  return (
    <div className="pre-launch-state">
      <div className="countdown-container">
        <h2>Launch Countdown</h2>
        <p>When the launch opens, start the journey to join the queue.</p>
      </div>

      <button onClick={() => void onStart()}>Join when available</button>

      <div className="launch-tips">
        <h3>Tips for Launch Day</h3>
        <ul>
          <li>Keep this page open - do not refresh</li>
          <li>Have your payment info ready</li>
          <li>Know your size before you enter</li>
          <li>You will have 15 minutes to complete checkout</li>
        </ul>
      </div>
    </div>
  );
}

function QueueState({ sequence, product }: { sequence: SequenceView; product: Product }) {
  return (
    <div className="queue-state">
      <div className="queue-header">
        <h2>You're in the Queue</h2>
        <p>Please keep this page open</p>
      </div>

      <div className="queue-stats">
        <div className="stat position">
          <span className="label">Status</span>
          <span className="value">{sequence.phase}</span>
        </div>

        <div className="stat wait-time">
          <span className="label">Next step</span>
          <span className="value">Watch for admission</span>
        </div>
      </div>

      <div className="product-reminder">
        <img src={product.imageUrl} alt={product.name} />
        <div className="product-details">
          <h4>{product.name}</h4>
          <p className="price">${product.price}</p>
        </div>
      </div>

      <div className="queue-tips">
        <p>While you wait:</p>
        <ul>
          <li>Review product details and sizing</li>
          <li>Prepare your payment method</li>
          <li>Do not close or refresh this page</li>
        </ul>
      </div>
    </div>
  );
}

function AdmittedState({
  sequence,
  product,
}: {
  sequence: Extract<SequenceView, { phase: "granted" }>;
  product: Product;
}) {
  const expiresAt = sequence.grant.expiresAt ? new Date(sequence.grant.expiresAt).getTime() : undefined;
  const [timeRemaining, setTimeRemaining] = useState(0);

  useEffect(() => {
    if (!expiresAt) return;

    const interval = setInterval(() => {
      const remaining = Math.max(0, expiresAt - Date.now());
      setTimeRemaining(remaining);

      if (remaining <= 0) {
        clearInterval(interval);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [expiresAt]);

  const handleCheckout = () => {
    // Navigate to checkout with admission context
    const admissionData = {
      admissionGrant: sequence.grant.token,
      productId: product.id,
    };

    sessionStorage.setItem("fanfare_admission", JSON.stringify(admissionData));
    window.location.href = `/checkout/${product.id}`;
  };

  return (
    <div className="admitted-state">
      <div className="success-banner">
        <div className="success-icon">✓</div>
        <h2>You're In!</h2>
        <p>Complete your purchase before time runs out</p>
      </div>

      <div className="admission-timer">
        <span className="timer-value">{formatTime(timeRemaining)}</span>
        <span className="timer-label">remaining</span>
      </div>

      <div className="product-card">
        <img src={product.imageUrl} alt={product.name} />
        <div className="details">
          <h3>{product.name}</h3>
          <p className="price">${product.price}</p>
        </div>
      </div>

      <button onClick={handleCheckout} className="checkout-btn">
        Complete Purchase
      </button>

      <p className="checkout-warning">Do not close this page until checkout is complete</p>
    </div>
  );
}

function QueueProgress({ position }: { position?: number }) {
  // Visual progress indicator
  const progress = position ? Math.max(0, 100 - Math.log10(position) * 25) : 0;

  return (
    <div className="queue-progress">
      <div className="progress-bar">
        <div className="progress-fill" style={{ width: `${progress}%` }} />
      </div>
      <div className="progress-labels">
        <span>Waiting</span>
        <span>Almost there</span>
        <span>Your turn</span>
      </div>
    </div>
  );
}

function formatWaitTime(seconds?: number): string {
  if (!seconds) return "Calculating...";

  if (seconds < 60) return "Less than a minute";
  if (seconds < 120) return "About 1 minute";

  const minutes = Math.floor(seconds / 60);
  if (minutes < 60) return `About ${minutes} minutes`;

  const hours = Math.floor(minutes / 60);
  const remainingMins = minutes % 60;
  return `${hours}h ${remainingMins}m`;
}

function formatTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;
  return `${minutes}:${seconds.toString().padStart(2, "0")}`;
}
```

## Step 4: Launch Day Operations

### Monitoring Dashboard

```typescript theme={null}
// Admin monitoring interface
interface QueueMetrics {
  currentQueueSize: number;
  totalEntered: number;
  totalAdmitted: number;
  totalCompleted: number;
  avgWaitTime: number;
  currentAdmissionRate: number;
  inventoryRemaining: number;
}

async function getQueueMetrics(queueId: string): Promise<QueueMetrics> {
  const response = await getLaunchMetrics(queueId);
  return response;
}

// Real-time updates via webhook or polling
function MonitoringDashboard({ queueId }: { queueId: string }) {
  const [metrics, setMetrics] = useState<QueueMetrics | null>(null);

  useEffect(() => {
    const interval = setInterval(async () => {
      const data = await getQueueMetrics(queueId);
      setMetrics(data);
    }, 5000);

    return () => clearInterval(interval);
  }, [queueId]);

  if (!metrics) return <Loading />;

  return (
    <div className="monitoring-dashboard">
      <MetricCard label="Queue Size" value={metrics.currentQueueSize} />
      <MetricCard label="Total Entered" value={metrics.totalEntered} />
      <MetricCard label="Admitted" value={metrics.totalAdmitted} />
      <MetricCard label="Completed" value={metrics.totalCompleted} />
      <MetricCard label="Avg Wait Time" value={formatDuration(metrics.avgWaitTime)} />
      <MetricCard label="Admission Rate" value={`${metrics.currentAdmissionRate}/min`} />
      <MetricCard label="Inventory Left" value={metrics.inventoryRemaining} />
    </div>
  );
}
```

### Emergency Controls

```typescript theme={null}
// Pause queue if issues arise
async function pauseQueue(queueId: string) {
  await updateLaunchExperience(queueId, {
    status: "paused",
  });
}

// Resume queue
async function resumeQueue(queueId: string) {
  await updateLaunchExperience(queueId, {
    status: "active",
  });
}

// Adjust admission rate dynamically
async function adjustAdmissionRate(queueId: string, newRate: number) {
  await updateLaunchExperience(queueId, {
    config: {
      admissionRatePerMinute: newRate,
    },
  });
}

// End queue early (e.g., sold out)
async function endQueueEarly(queueId: string, reason: string) {
  await updateLaunchExperience(queueId, {
    status: "ended",
    endReason: reason,
  });

  // Notify remaining users
  await notifyWaitingConsumers(queueId, {
    type: "experience_ended",
    message: "This product has sold out. Thank you for your interest.",
  });
}
```

## Step 5: Inventory Sync

### Track Purchases in Real-Time

```typescript theme={null}
// Webhook handler for order completion
async function handleOrderCompleted(orderId: string, queueId: string) {
  // Update inventory count
  const remainingInventory = await decrementInventory(orderId);

  // Check if sold out
  if (remainingInventory <= 0) {
    await endQueueEarly(queueId, "sold_out");
  }

  // Update queue display if showing remaining stock
  await updateLaunchMetadata(queueId, {
    inventoryRemaining: remainingInventory,
  });
}

// Sync inventory periodically
async function syncInventoryWithQueue(queueId: string) {
  const actualInventory = await getActualInventoryCount();
  const queueMetadata = await getLaunchExperience(queueId);

  if (actualInventory !== queueMetadata.metadata.inventoryRemaining) {
    await updateLaunchMetadata(queueId, {
      inventoryRemaining: actualInventory,
    });

    if (actualInventory <= 0 && queueMetadata.status === "active") {
      await endQueueEarly(queueId, "sold_out");
    }
  }
}
```

## Step 6: Post-Launch Analytics

### Generate Launch Report

```typescript theme={null}
interface LaunchReport {
  summary: {
    totalVisitors: number;
    totalEntered: number;
    totalAdmitted: number;
    totalPurchased: number;
    conversionRate: number;
    avgWaitTime: number;
    peakQueueSize: number;
    revenue: number;
  };
  timeline: Array<{
    timestamp: Date;
    entered: number;
    admitted: number;
    purchased: number;
  }>;
  dropoffAnalysis: {
    leftBeforeAdmission: number;
    expiredAdmissions: number;
    abandonedCart: number;
  };
}

async function generateLaunchReport(queueId: string): Promise<LaunchReport> {
  const analytics = await getLaunchReport(queueId);

  return {
    summary: {
      totalVisitors: analytics.uniqueVisitors,
      totalEntered: analytics.totalEntered,
      totalAdmitted: analytics.totalAdmitted,
      totalPurchased: analytics.totalCompleted,
      conversionRate: (analytics.totalCompleted / analytics.totalEntered) * 100,
      avgWaitTime: analytics.avgWaitTimeSeconds,
      peakQueueSize: analytics.peakQueueSize,
      revenue: analytics.totalRevenue,
    },
    timeline: analytics.hourlyBreakdown,
    dropoffAnalysis: {
      leftBeforeAdmission: analytics.totalEntered - analytics.totalAdmitted - analytics.currentlyWaiting,
      expiredAdmissions: analytics.totalAdmitted - analytics.totalCompleted,
      abandonedCart: analytics.checkoutStarted - analytics.totalCompleted,
    },
  };
}
```

## Best Practices

### 1. Communicate Clearly

```tsx theme={null}
// Show clear messaging at each stage
const stageMessages = {
  preQueue:
    "You're early! The launch begins at {time}. Stay on this page to automatically enter the queue when it opens.",
  entering: "Connecting you to the queue. This should only take a moment.",
  waiting: "You're in line! Your position: {position}. Estimated wait: {time}. Do not close this page.",
  admitted: "It's your turn! You have {time} to complete your purchase.",
  completed: "Thank you for your purchase! Order confirmation has been sent to your email.",
  expired:
    "Your checkout window has expired. The product may still be available - return to the product page to try again.",
};
```

### 2. Mobile Optimization

```css theme={null}
/* Ensure experience works well on mobile */
.launch-experience {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.queue-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.checkout-btn {
  width: 100%;
  padding: 16px;
  font-size: 18px;
  position: sticky;
  bottom: 20px;
}

@media (max-width: 480px) {
  .queue-stats {
    grid-template-columns: 1fr;
  }
}
```

### 3. Handle Edge Cases

```typescript theme={null}
// Handle page visibility changes
useEffect(() => {
  const handleVisibilityChange = () => {
    if (document.visibilityState === "visible" && stage === "participating") {
      // Refresh position when returning to page
      if (view?.journeyStage === "routed") {
        void view.reroute();
      }
    }
  };

  document.addEventListener("visibilitychange", handleVisibilityChange);
  return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
}, [view, stage]);

// Handle network reconnection
useEffect(() => {
  const handleOnline = () => {
    if ((stage === "participating" || stage === "admitted") && view?.journeyStage === "routed") {
      void view.reroute();
    }
  };

  window.addEventListener("online", handleOnline);
  return () => window.removeEventListener("online", handleOnline);
}, [view, stage]);
```

## Troubleshooting

### Queue Not Starting on Time

1. Verify scheduled start time includes timezone
2. Check server clock synchronization
3. Ensure queue status is "scheduled" not "draft"

### High Drop-off Rate

1. Review wait time estimates - are they accurate?
2. Check for mobile experience issues
3. Ensure admission window is long enough
4. Consider sending browser notifications

### Inventory Mismatch

1. Implement real-time inventory sync
2. Add safety buffer (e.g., hold back 5%)
3. Monitor webhook delivery for order completion

## What's Next

* [Flash Sale Guide](/guides/use-cases/flash-sale) - Time-limited sales events
* [Limited Edition](/guides/use-cases/limited-edition) - Exclusive product drops
* [Webhooks Guide](/guides/advanced/webhooks-guide) - Real-time event handling
