Modern website mascots, like the kind covered in the AI mascot strategy reference, almost all run on Rive. This is the hands-on companion to that: how Rive compares to Lottie, how the pieces fit together, how to keep the files small, how to wire it into your framework, and how to make sure the mascot never becomes the reason a page feels slow.
What Rive is, and isn't
Rive is two things bundled. The first is a vector animation authoring tool — a desktop and web editor where designers build characters with bones, blend states, and procedural animation. The second is a runtime that loads the binary .riv file and drives the animation in a browser, native mobile app, or game engine.
The runtime is the part most engineers care about. Rive's web runtime is roughly 30-50 KB compressed (the WASM core plus a small JS wrapper), and it loads .riv files which are themselves typically 5-50 KB for a single-character mascot. Total page weight for a working mascot: 50-100 KB.
What Rive is not: a video player, a 3D engine, or a generic animation library. It is purpose-built for interactive vector animation with state-machine logic. If your animation is a 30-second logo loop with no interactivity, Lottie or a single CSS keyframe is simpler. If your animation needs to respond to events, change states, or react to inputs from JavaScript, Rive is the right runtime.
Rive vs Lottie: the honest comparison
| Feature | Rive | Lottie |
|---|---|---|
| Typical mascot file size | 5-50 KB | 20-200 KB |
| Built-in state machine editor | Yes | No |
| GPU-composited rendering | Yes | Partial |
| Frame-cost on mid-tier mobile | < 1ms | 3-7ms |
| Native mobile runtimes | Yes | Yes |
| Designer tool maturity | High (Rive Editor) | High (After Effects + Bodymovin) |
| React integration | @rive-app/react-canvas | lottie-react / @lottiefiles/react-lottie |
| Vue / Svelte / Solid integration | Yes | Yes |
| Server-side rendering | Partial | Partial |
| Vector quality at scale | Yes | Yes |
| After Effects import | No | Yes |
| Free runtime | Yes | Yes |
| Editor pricing | Free / $14-79/mo team | Free (After Effects subscription) |
| License | MIT (runtime) | MIT (runtime) |
| WASM-based runtime | Yes | No |
| Native interaction events | Yes | No |
| prefers-reduced-motion handling | Manual | Manual |
| 2026 mascot ecosystem mindshare | Dominant | Legacy |
Pricing and feature data last verified:
The honest verdict: Rive wins on bundle size, performance, and state-machine-driven interactivity. Lottie wins on After Effects import (designers with existing AE workflows) and on the legacy ecosystem of pre-built animations. For new mascot work in 2026, Rive is the default; for "I have a 5-year-old Lottie animation I need to keep working," Lottie is fine.
There is a fuller comparison in the Rive vs Lottie for chat widgets piece.
The Rive web architecture
A running Rive animation in a browser has four pieces.
-
The
.rivfile. A binary asset built in Rive Editor. Contains the artboard, the bones, the timelines, the state machine, and any embedded raster assets. Loaded over HTTP like any other static asset; cache aggressively. -
The WASM runtime (
rive.wasm). The actual animation engine, compiled to WebAssembly. Loaded once per page; subsequent mascots share the runtime. -
The JS wrapper. Either
@rive-app/canvas(vanilla JS),@rive-app/react-canvas(React), or the equivalent for your framework. Handles the canvas creation, the state-machine inputs, the play / pause / dispose lifecycle. -
A
<canvas>element in the DOM. The render target. Width and height should be explicit (no CSS-based sizing; reserve the layout slot).
The four pieces work together: the JS wrapper creates the canvas, fetches the .riv file, hands it to the WASM runtime, the runtime drives the render loop, the canvas paints. Behavior events from the host page (scroll, dwell, click) flip state-machine inputs; the runtime resolves the new state and continues rendering.
React integration
'use client';
import { useRive, useStateMachineInput } from '@rive-app/react-canvas';
export function Mascot {
const { rive, RiveComponent } = useRive({
src: '/mascot.riv',
stateMachines: 'MascotStateMachine',
autoplay: true,
});
// Optional: drive a state-machine input from React state
const speakingInput = useStateMachineInput(rive, 'MascotStateMachine', 'isSpeaking');
return (
<div style={{ width: 96, height: 96 }}>
<RiveComponent />
</div>
);
}
Three rules.
Rule 1: explicit width and height on the wrapper. The <canvas> Rive renders into respects its parent's bounds; if the parent is auto-sized, the canvas resizes mid-load and breaks CLS. Set width and height either inline or via CSS that resolves before first paint.
Rule 2: 'use client' for the component. Rive owns canvas state; it cannot be a server component in Next.js App Router. The pattern is a server component for the page shell, a client component for the mascot.
Rule 3: dispose on unmount. useRive handles disposal automatically in React; manual cleanup is only needed for non-React or class-based integrations. If you forget to dispose in a non-React stack, the WASM heap leaks.
Next.js integration
Next.js App Router with Rive is straightforward but has two gotchas.
Gotcha 1: dynamic import to keep the mascot off the LCP path. The Rive runtime is 30-50 KB and adds to your initial JS bundle if statically imported. The fix:
// app/components/MascotLoader.tsx
'use client';
import dynamic from 'next/dynamic';
const Mascot = dynamic( => import('./Mascot').then((m) => m.Mascot), {
ssr: false,
loading: => <div style={{ width: 96, height: 96 }} aria-hidden />,
});
export { Mascot };
This pushes the Rive runtime to a separate chunk, loaded after the LCP event.
Gotcha 2: the .riv file in public/. Place the file in public/mascot.riv and reference it with an absolute path (/mascot.riv). Importing the binary as a module works in some configurations but breaks Turbopack and some webpack setups; public/ is the portable path.
The deeper Next.js pattern, including SSR / streaming-rendering compatibility, is in the Rive in Next.js — use client patterns article.
Other framework integrations
Vue. @rive-app/canvas works directly with Vue 3 — wrap the canvas creation in onMounted / onUnmounted lifecycle hooks. The community vue-rive wrapper is also available but adds a dependency.
Svelte. Same pattern as Vue — @rive-app/canvas in onMount / onDestroy. Svelte's bundle output is small enough that the framework overhead is minimal.
Solid. @rive-app/canvas directly. Solid's reactivity model maps cleanly to state-machine inputs.
Vanilla JS / Shopify Liquid / WordPress. @rive-app/canvas via a <script> tag. The pattern for Shopify (which has no native module bundler in the theme runtime) is in the Rive in Shopify article.
What a Rive state machine does
The state machine is what makes Rive useful for mascots. Without it, you have a sequence of timelines that play in order — a video, basically. With it, you have a graph of states the runtime moves between based on what the visitor is doing.
A typical mascot state machine has 4-7 states:
- Idle (default — gentle breathing, occasional blink)
- Attentive (the mascot has noticed the visitor — turn head, eye contact)
- Speaking (the agent is delivering a message — mouth animation, gesturing)
- Celebrating (visitor converted — fireworks, jump)
- Dismissed (visitor closed the chat — wave goodbye, fade)
- Sleeping (long idle — eyes closed, gentle bob)
Inputs to the state machine — boolean flags or numeric values JavaScript can flip — drive transitions. A trigger from the agent ("the visitor just scrolled past 50%") flips an isAttentive boolean; the runtime resolves the new state and animates between idle and attentive over a 200ms blend period.
The state-machine editor is in Rive's authoring tool. The handoff to engineering is a .riv file plus a documented list of input names and their semantic meanings. The deep dive is in the Rive state machines article.
File size discipline
The 50 KB budget for a Rive mascot is achievable but requires discipline.
Use vector primitives, not embedded raster. A character drawn in Rive's vector tools is 5-15 KB. The same character with a raster face import is 80-200 KB. Most file-size problems trace back to a single embedded high-res image.
Limit the number of bones. A mascot with 25 bones renders fast but compiles to a larger .riv file than the same character with 12 bones. Each bone costs roughly 0.5-1 KB.
Compress the .riv over HTTP. Rive files compress well with gzip / brotli; ensure your CDN serves them with the right encoding header.
Defer non-critical states. If your mascot has 12 states but only 5 fire on the typical visit, consider splitting into two .riv files and lazy-loading the rare states.
The full optimization checklist is in the Rive file size optimization article.
How to keep a mascot from hurting Core Web Vitals
A mascot is a common reason a site slips on Core Web Vitals. Five rules keep it clean.
-
Reserve the layout slot before the canvas loads. Explicit width and height on the wrapper. Skip this and CLS goes from 0 to 0.04 the moment the canvas mounts.
-
Defer mascot initialization to after LCP. Use
requestIdleCallbackor asetTimeout(..., 200)after the LCP event. The mascot does not contend with the hero image for paint. -
Don't block hydration on the mascot. The mascot is a client component; the rest of the page should hydrate first. The
dynamicpattern above handles this in Next.js. -
Pause when off-screen. Use
IntersectionObserverto pause the mascot's animation when it's outside the viewport. Wasted GPU cycles cost INP on mid-tier mobile devices. -
Honor
prefers-reduced-motion. Start in a static frame if the OS setting requests reduced motion.
Implemented correctly, the mascot adds 0 ms to LCP, 0-4 ms to INP, and 0 to CLS. Implemented carelessly, it adds 80-200 ms to LCP, 30-50 ms to INP, and 0.02-0.06 to CLS — enough to fail a Core Web Vitals check.
Further reading
- GuideAI website mascot — strategy and ROIWhy Rive is the right runtime for the 2026 mascot category.
- GuideCwv discipline for ecommerceThe Core Web Vitals discipline this guide implements.
- BlogRive state machines — behavioral logic without re-renderingThe state-machine deep dive.
- BlogRive in Next.js — `use client` patternsThe Next.js App Router integration deep dive.
Frequently asked questions
An animation runtime and authoring tool optimized for interactive vector animation. Uses a binary .riv format with a built-in state-machine system. Web runtime is roughly 30-50 KB compressed.
Last updated May 31, 2026.