Skip to content

Live demo

The reference application at apps/web in the monorepo demonstrates the Headless Media SDK end to end. It consumes only published package exports — no direct packages/*/src imports.

What it demonstrates

FeatureSDK pieces used
Multi-provider switchingProviderSelector + createMediaClientPexelsMediaClient / PixabayMediaClient
Capability-driven UIuseMediaCapabilities gates tabs and filters — never provider === checks
Photo and video searchuseMediaSearch, useMediaVideos
Submit-to-search patternenabled defer on hooks
PaginationPagination + nextPage / previousPage
Photo and video previewsPhotoPreview, VideoPreview
View trackingclient.trackView on preview open
Curated photos tabuseCuratedPhotos (demo-local hook) gated by capabilities.operations.curatedPhotos
Orientation and size filtersGated by capabilities.photoFilters.orientation and capabilities.photoFilters.size
Loading, error, and empty statesLoadingState, ErrorState
Missing API key handlingAppSetup with per-provider env instructions

Architecture

mermaid
flowchart LR
  web["apps/web"]
  react["@media-sdk/react"]
  ui["@media-sdk/ui-react"]
  core["@media-sdk/core"]

  web --> react
  web --> ui
  react --> core
  ui --> core
LayerRole
apps/webDemo shell: provider selector, env wiring, createMediaClient, search UI composition
@media-sdk/reactMediaProvider, useMediaSearch, useMediaVideos, useMediaCapabilities
@media-sdk/ui-reactPresentational components (grids, previews, pagination, states)
@media-sdk/corePexelsMediaClient, PixabayMediaClient, types, and HTTP transport

Provider switch flow: MediaAppShell holds the active provider in state. Selecting a new provider builds a fresh client via createMediaClient, passes it to MediaProvider, and remounts MediaApp with key={provider} so tab, filter, and preview state reset cleanly.

Prerequisites

Environment setup

Copy the example env file and add your API keys:

bash
cp apps/web/.env.example apps/web/.env

Edit apps/web/.env:

env
VITE_PEXELS_API_KEY=your_pexels_api_key_here
VITE_PIXABAY_API_KEY=your_pixabay_api_key_here
# Optional default when both keys are set (pexels | pixabay)
VITE_MEDIA_PROVIDER=pexels
VariableRequiredDescription
VITE_PEXELS_API_KEYWhen using PexelsPexels API key exposed to the Vite client bundle
VITE_PIXABAY_API_KEYWhen using PixabayPixabay API key exposed to the Vite client bundle
VITE_MEDIA_PROVIDERNoDefault provider when both keys are set (pexels or pixabay; defaults to pexels)

At least one key must be set. Without any key, the app renders AppSetup with setup instructions. If the selected provider's key is missing, AppSetup shows which env var to set for that provider (and still shows the provider selector when another key is configured).

Run locally

From the monorepo root, install dependencies:

bash
pnpm install

Build SDK packages before the first run (or after package changes):

bash
pnpm --filter @media-sdk/core build
pnpm --filter @media-sdk/react build
pnpm --filter @media-sdk/ui-react build

Start the dev server:

bash
pnpm --dir apps/web dev

Open the URL printed by Vite (typically http://localhost:5173).

Production build

bash
pnpm --dir apps/web build

Output is written to apps/web/dist.

Using published npm packages

The demo in the monorepo uses workspace packages during development. To build a similar app with published npm packages:

bash
pnpm create vite my-media-app --template react-ts
cd my-media-app
pnpm add @media-sdk/core@^0.3.0 @media-sdk/react@^0.3.0 @media-sdk/ui-react@^0.3.0

Copy .env with VITE_PEXELS_API_KEY (and optionally VITE_PIXABAY_API_KEY) and follow the Quick Start guide.

Feature walkthrough

Provider switching

Use the Provider dropdown in the header to switch between Pexels and Pixabay at runtime — no page reload. Providers without a configured API key appear disabled in the selector.

Switching providers:

  • Swaps the MediaClient inside MediaProvider
  • Remounts the search UI so incompatible tab and filter state is cleared
  • Re-renders capability-gated controls from useMediaCapabilities() (curated tab, size filter, etc.)

Provider names appear only in the selector label and env setup text — feature visibility is never gated with if (provider === "pexels"). See Capabilities and Provider comparison.

Submit a query from the search bar. Photo results use useMediaSearch; video results use useMediaVideos when the Videos tab is active. The hook uses enabled: submittedQuery.length > 0 so no request fires until the user submits.

Curated tab

When the active client supports curated photos (capabilities.operations.curatedPhotos), a Curated tab appears. This tab is hidden automatically for providers that do not support the operation (for example, Pixabay).

Filters

  • Orientation — shown only when capabilities.photoFilters.orientation is true
  • Size — shown only when capabilities.photoFilters.size is true (Pexels only)

Filter values are passed to useMediaSearch via photoFilters. Switching to Pixabay hides unsupported controls automatically.

Pagination

Pagination controls call nextPage / previousPage from the active hook. Changing video pages clears any open video preview.

Preview and trackView

Selecting a photo or video opens a preview dialog. trackPhotoPreviewOpen / trackVideoPreviewOpen call client.trackView once per preview open — not on search or pagination. See Events.

Error, empty, and loading states

  • LoadingLoadingState while a request is in flight
  • ErrorErrorState with the API error message
  • Empty — inline message when a search returns no results
  • UnconfiguredAppSetup when no API keys are set, or when the selected provider's key is missing

Testing

bash
# Web app tests
pnpm --dir apps/web test

# Monorepo-wide
pnpm test
pnpm typecheck

Integration tests under apps/web/src/integration/ exercise full search → preview → trackView flows against mocked HTTP responses.

Next steps