@meshless/react is a thin React wrapper around the Meshless embed. You pass a
projectId and get an interactive, cookieless 360° viewer that streams
pre-rendered frames from the Meshless CDN — no Three.js, no WebGL, no client 3D code.
1. Install
npm install @meshless/react
# or: pnpm add @meshless/react
# or: yarn add @meshless/react
The package ships ESM + CJS builds and its own TypeScript types. Its only peer
dependency is react (18 or 19).
2. Render the viewer
import { MeshlessViewer } from "@meshless/react";
export function ProductViewer() {
return (
<MeshlessViewer
projectId="YOUR_PROJECT_ID"
autoplay
showHotspots
style={{ width: "100%", aspectRatio: "1" }}
/>
);
}
Under the hood the component renders a
<div class="meshless-viewer" data-project-id="…"> and injects the embed script
from https://cdn.meshless.io/embed/v1/embed.js once per page. The script
then hydrates every viewer on the page.
3. Next.js (App Router)
MeshlessViewer is SSR-safe: on the server it renders the plain wrapper div
and defers script injection to a client useEffect. Because it uses a hook,
render it inside a Client Component — add "use client" at the top of the
file that uses it:
"use client";
import { MeshlessViewer } from "@meshless/react";
export function HeroViewer({ projectId }: { projectId: string }) {
return (
<MeshlessViewer
projectId={projectId}
autoplay
autorotateSpeed={8}
style={{ width: "100%", aspectRatio: "4 / 3" }}
/>
);
}
You can then use <HeroViewer /> from any Server Component. No next/dynamic
and no ssr: false workaround is needed — the viewer simply mounts on the client.
4. Next.js (Pages Router) & other setups
In the Pages Router (or Vite, CRA, Remix, etc.) just import and render the component — it works anywhere React runs. There is nothing server-specific to configure.
Props
Only projectId is required. Every omitted prop falls back to the viewer
defaults you saved for the project in the dashboard.
| Prop | Type | Description |
|---|---|---|
projectId | string | Required. The Meshless project id. |
bgColor | string | null | Any CSS colour for the stage background. |
hideControls | boolean | Hide the on-screen rotation controls. |
showHotspots | boolean | Show the project's hotspots (default true). |
fullscreen | boolean | Show a fullscreen toggle. |
autoplay | boolean | Start auto-rotating on load. |
autorotateSpeed | number | Frames per second while auto-rotating (default 10). |
autorotateDirection | 'cw' | 'ccw' | Rotation direction (default 'cw'). |
autorotatePauseOnHover | boolean | Pause while hovered (default true). |
autorotateResumeDelay | number | Ms after interaction before resuming. |
sensitivity | number | Horizontal drag: pixels per frame step. |
verticalSensitivity | number | Vertical drag: pixels per row step. |
invertX / invertY | boolean | Invert drag directions. |
momentum | boolean | Velocity-based deceleration after release. |
momentumFriction | number | 0–1 deceleration per animation frame. |
wheel | boolean | Mouse-wheel rotation (default true). |
keyboard | boolean | Arrow-key navigation (default true). |
initialFrame / initialRow | number | Starting frame / row indices. |
className / style | React standard | Applied to the wrapper div. |
Requirements
- The project must be Ready (frames encoded and uploaded).
- If your workspace uses a domain allowlist, the page's origin must be allowed (Dashboard → Domains). Otherwise the viewer will refuse to load.
Need full control?
If you host your own frame sequences (for example, exported with the Meshless
converter) and want a fully controlled component with hotspot editing, colorways,
and lazy loading, use @meshless/viewer
instead of this wrapper.
What's next?
- Embed Options — every
data-*attribute the props map to - JavaScript SDK — control the viewer at runtime and listen for events
- Variants — publish alternate renders for colors and finishes