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.

Ask a question... ⌘I