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

# React (recommended)

Type-safe <MotionessImg> binding for React 18+ and Next.js — the full effect set out of the box.

# React binding

**This is the recommended way to embed Motioness assets.** The React component ships with everything other surfaces don't have:

- **Hover effects** built into `mode="animate-on-hover"` — pause + reset on `mouseleave`.
- **Lazy loading** via `loading="lazy"` and `IntersectionObserver`.
- **`onReady` / `onError` callbacks** for analytics or fallback UI.
- **SSR-safe** — works in Next.js App Router server components without `'use client'` at the page level.
- **Signed-URL friendly** — accepts precomputed `signature` + `exp` so secrets stay server-side.

`@motioness/proxy-client/react` wraps the web component with a typed React component. The underlying element progressively enhances after JS loads, so server-rendered HTML stays clean.

## Install

```bash
npm install @motioness/proxy-client
```

Peer deps: `react` >= 18, `react-dom` >= 18.

## Basic use

```tsx
import { MotionessImg } from '@motioness/proxy-client/react';

export function Hero() {
  return (
    <MotionessImg
      projectKey="pk_abc"
      src="https://cdn.acme.com/hero.png"
      motion="medium"
      mode="loop"
      durSeconds={4}
      style={{ width: '100%', borderRadius: 12 }}
    />
  );
}
```

## Props

All `buildProxyUrl` options + standard HTML attributes:

```ts
type MotionessImgProps = {
  projectKey: string;
  src: string;
  motion?: 'subtle' | 'medium' | 'kinetic';
  mode?: 'loop' | 'animate-in' | 'animate-on-hover';
  durSeconds?: number;
  prompt?: string;
  seed?: number;
  aspect?: '1:1' | '16:9' | '9:16' | 'preserve';
  idempotencyKey?: string;
  waitMs?: number;
  signature?: string;
  exp?: number;
  loading?: 'lazy' | 'eager';
  origin?: string;

  onReady?: (videoEl: HTMLVideoElement) => void;
  onError?: (err: Error) => void;

  // Standard HTML props (style, className, id, ...)
} & React.HTMLAttributes<HTMLElement>;
```

## Server signing

Sign URLs in a server component (Next.js App Router) and pass them as a precomputed `signature` + `exp`:

```tsx
// app/hero.tsx (server component)
import { motionessSignedUrl } from '@/lib/motioness';
import { MotionessImg } from '@motioness/proxy-client/react';

export default async function Hero() {
  const { signature, exp } = await motionessSignedUrl(
    'https://cdn.acme.com/hero.png',
    3600,
  );

  return (
    <MotionessImg
      projectKey={process.env.NEXT_PUBLIC_MOTIONESS_PROJECT_KEY!}
      src="https://cdn.acme.com/hero.png"
      signature={signature}
      exp={exp}
    />
  );
}
```

The component runs on the client (the `<motioness-img>` element does), but the props were computed server-side, so the secret never leaks.

## Dynamic source

```tsx
'use client';

import { useState } from 'react';
import { MotionessImg } from '@motioness/proxy-client/react';

export function VariantPicker({ src }: { src: string }) {
  const [seed, setSeed] = useState(0);

  return (
    <>
      <MotionessImg
        projectKey="pk_abc"
        src={src}
        idempotencyKey={`variant-${seed}`}
        seed={seed}
      />
      <button onClick={() => setSeed((s) => s + 1)}>Try another</button>
    </>
  );
}
```

Each `seed` increment produces a fresh asset (different `idem` → different `asset_id`).

## Lazy load

```tsx
<MotionessImg projectKey="..." src="..." loading="lazy" />
```

Defers mp4 load until the element scrolls into view (`IntersectionObserver`). Poster jpg loads eagerly.

## Next.js

Works in App Router and Pages Router. The component is client-only — Next.js will bundle it for the client.

For App Router, mark the parent component `'use client'` if you need event callbacks (`onReady`, `onError`). For purely declarative use (no callbacks), it works in server components.

## Avoiding hydration mismatches

The element progressively enhances after JS loads. There's no mismatch because nothing renders server-side beyond the wrapper element. Just be careful not to set props that read from `Date.now()` on the server — the server-rendered URL would differ from the client-rendered URL.

For signed URLs, sign once on the server and pass `signature` + `exp` as static props (don't sign in a `useEffect`).

## Tree-shaking

The React entry is ~500B + the element (~2KB). Importing only `MotionessImg` won't pull in `buildProxyUrl` if you don't use it.