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 onmouseleave. - Lazy loading via
loading="lazy"andIntersectionObserver. onReady/onErrorcallbacks 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+expso 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
bashnpm install @motioness/proxy-client
Peer deps: react >= 18, react-dom >= 18.
Basic use
tsximport { 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:
tstype 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.