Cómo usar Rive en React y Next.js
Por el equipo de Charmotion · Actualizado el

Para usar Rive en React, instala @rive-app/react-webgl2, llama a useRive({ src, stateMachine, autoplay: true }) y renderiza el RiveComponent que devuelve. Cambia de estado con useStateMachineInput en archivos basados en inputs o con los hooks de view model en archivos con Data Binding. En Next.js, renderízalo en un componente de cliente.
Puntos clave
- Rive recomienda @rive-app/react-webgl2 para la mayoría de los proyectos; react-canvas sigue funcionando.
- useRive devuelve la instancia de rive y un RiveComponent para renderizar.
- useStateMachineInput controla los inputs heredados; los hooks de view model controlan los archivos con Data Binding.
- En Next.js, coloca Rive en un componente con "use client" o cárgalo con ssr: false.
¿Qué paquete de Rive para React debo instalar?
Rive publica tres paquetes de React desde el repositorio rive-react. Comparten la misma API y se diferencian en cómo dibujan:
| Paquete | Renderizador | Cuándo usarlo |
|---|---|---|
@rive-app/react-webgl2 | Rive Renderer sobre WebGL2 | Opción predeterminada; admite todas las funciones, incluido el vector feathering |
@rive-app/react-canvas | Canvas2D del navegador | Muchas instancias en una misma página, o donde WebGL no está disponible |
@rive-app/react-canvas-lite | Canvas2D, con un conjunto reducido de funciones | La descarga más liviana cuando el archivo no necesita las funciones que faltan |
npm install @rive-app/react-webgl2
La documentación web de Rive ahora recomienda usar WebGL2 en la mayoría de los casos. Una advertencia: los navegadores limitan la cantidad de contextos WebGL por página. Si una página muestra muchas animaciones de Rive a la vez, usa el paquete canvas o pasa useOffscreenRenderer: true para que todas las instancias compartan un único contexto offscreen.
El runtime es un módulo WASM, así que es la parte que más pesa. Rive indica que el WASM de canvas pesa 567 KB y el build de WebGL2, 648 KB tras la compresión brotli (enero de 2026). Cárgalo solo en las rutas que lo necesiten.
¿Cómo renderizo un archivo .riv con useRive?
Coloca el .riv en tu carpeta de archivos estáticos (por ejemplo public/character.riv) y llama a useRive. El hook devuelve la instancia rive, que es null hasta que el archivo carga, y un RiveComponent que renderiza el canvas.
import { useRive } from "@rive-app/react-webgl2";
export function Character() {
const { rive, RiveComponent } = useRive({
src: "/character.riv",
stateMachine: "CharacterStateMachine",
autoplay: true,
});
return (
<div style={{ width: 240, height: 240 }}>
<RiveComponent aria-label="Animated character" />
</div>
);
}
RiveComponent ocupa todo su contenedor padre, así que define el tamaño del contenedor, no del canvas. Dale una etiqueta accesible si transmite información, o escóndelo de las tecnologías de asistencia si es puramente decorativo.
Usa la opción en singular stateMachine. El runtime web de Rive reemplazó el parámetro en plural stateMachines en la v2.41.0, y los tutoriales antiguos todavía muestran la forma en plural. Si omites la máquina de estados (state machine), el archivo reproduce su animación predeterminada y tus inputs no responderán.
¿Cómo cambio de estado desde React?
La forma de controlar el archivo depende de lo que el archivo expone.
Archivos con inputs de máquina de estados (legacy)
useStateMachineInput(rive, stateMachineName, inputName) devuelve el input cuando el archivo ya cargó. Asigna .value en números y booleanos; llama a .fire() en los triggers.
import { useEffect } from "react";
import { useRive, useStateMachineInput } from "@rive-app/react-webgl2";
const SM = "CharacterStateMachine";
export function Character({ mood, successCount }: { mood: number; successCount: number }) {
const { rive, RiveComponent } = useRive({ src: "/character.riv", stateMachine: SM, autoplay: true });
const moodInput = useStateMachineInput(rive, SM, "mood");
const success = useStateMachineInput(rive, SM, "success");
useEffect(() => {
if (moodInput) moodInput.value = mood;
}, [moodInput, mood]);
// Fire the one-shot whenever the parent bumps successCount.
useEffect(() => {
if (successCount > 0) success?.fire();
}, [success, successCount]);
return (
<div style={{ width: 240, height: 240 }}>
<RiveComponent />
</div>
);
}
Archivos con view models (Data Binding)
Pasa autoBind: true y luego usa los hooks de view model con rive?.viewModelInstance:
const { rive, RiveComponent } = useRive({ src: "/character.riv", stateMachine: SM, autoplay: true, autoBind: true });
const vmi = rive?.viewModelInstance;
const { value: mood, setValue: setMood } = useViewModelInstanceNumber("mood", vmi);
const { trigger: celebrate } = useViewModelInstanceTrigger("success", vmi);
También puedes disparar un trigger directamente desde un manejador de eventos, como el manejador de clic de un botón, con success?.fire().
Rive recomienda Data Binding para proyectos nuevos, pero los archivos basados en inputs siguen funcionando en React. Usa la API que corresponda al archivo.
Ambos estilos de hooks no devuelven nada útil hasta que el archivo carga, así que protege cada llamada con encadenamiento opcional. Controla los valores desde props o estado, como en el useEffect de arriba, en lugar de llamar al runtime durante el render. Así la animación se mantiene sincronizada con el estado de tu UI y es fácil probar el componente con props simples.
¿Cómo uso Rive en el App Router de Next.js?
Rive dibuja en un canvas dentro del navegador, así que debe ejecutarse en un componente de cliente. En el App Router, los componentes son componentes de servidor por defecto. Marca el archivo que llama a useRive con "use client" al inicio:
"use client";
import { useRive } from "@rive-app/react-webgl2";
export default function HeroCharacter() {
const { RiveComponent } = useRive({
src: "/character.riv",
stateMachine: "CharacterStateMachine",
autoplay: true,
});
return <div className="h-60 w-60"><RiveComponent /></div>;
}
Luego impórtalo desde una página de servidor como de costumbre. La página se sigue renderizando en el servidor; solo este componente se hidrata en el navegador.
Mantén el .riv en public/ para que se sirva como recurso estático con una URL estable. Reserva el tamaño del contenedor en CSS para evitar un layout shift (salto de diseño) cuando aparezca el canvas. En una animación de la sección hero, una imagen estática de póster detrás del canvas evita un recuadro vacío durante la carga.
En el Pages Router aplica la misma regla: Rive solo debe ejecutarse en el navegador. Si ves errores del lado del servidor, usa el patrón de importación dinámica de la siguiente sección.
¿Cómo soluciono "window is not defined"?
El error significa que código que necesita variables globales del navegador se ejecutó durante el renderizado en el servidor. Con Rive suele ocurrir cuando un componente de servidor renderiza el componente, o cuando el paquete de Rive se importa en un módulo que el servidor evalúa.
Soluciones, en orden de preferencia:
- Asegúrate de que el componente que importa Rive empiece con
"use client". - Si el error persiste, carga el componente solo en el navegador con
next/dynamic:
"use client";
import dynamic from "next/dynamic";
const HeroCharacter = dynamic(() => import("./HeroCharacter"), {
ssr: false,
loading: () => <img src="/character-poster.png" alt="" width={240} height={240} />,
});
En las versiones recientes de Next.js, ssr: false solo se permite dentro de un componente de cliente; por eso el contenedor de arriba también está marcado con "use client".
- Nunca accedas a
window,documentni a la instancia de Rive en el nivel superior del módulo. Hazlo dentro deuseEffecto de manejadores de eventos, que solo se ejecutan en el navegador.
La carga diferida con ssr: false también deja el runtime WASM fuera del bundle inicial, lo que ayuda en páginas donde la animación no se ve sin hacer scroll.
¿Cómo respeto prefers-reduced-motion?
Los usuarios pueden pedirle al sistema operativo que reduzca el movimiento, y los navegadores lo exponen mediante la media query prefers-reduced-motion, ampliamente disponible desde 2020. Respétala en cualquier personaje en loop.
"use client";
import { useEffect, useState } from "react";
function usePrefersReducedMotion() {
const [reduce, setReduce] = useState(false);
useEffect(() => {
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
setReduce(mq.matches);
const onChange = () => setReduce(mq.matches);
mq.addEventListener("change", onChange);
return () => mq.removeEventListener("change", onChange);
}, []);
return reduce;
}
export function MaybeAnimated() {
const reduce = usePrefersReducedMotion();
return reduce
? <img src="/character-poster.png" alt="" width={240} height={240} />
: <Character mood={0} successCount={0} />;
}
Mostrar una imagen estática cuando se pide movimiento reducido es la opción más segura, y además evita descargar el runtime. Si el personaje comunica un estado, como la carga, conserva ese significado en texto o en un mensaje de estado accesible. Consulta animación accesible y movimiento reducido para la parte de WCAG.
Agrega una animación de Rive a una app de React o Next.js
- Instala el runtime. Ejecuta
npm install @rive-app/react-webgl2(o@rive-app/react-canvassi tienes muchas instancias en una página). - Agrega el archivo. Coloca el .riv en
public/para que se sirva en una URL estable, como/character.riv. - Crea un client component. Empieza el archivo con
"use client"y llama auseRive({ src, stateMachine, autoplay: true }). - Renderízalo y define su tamaño. Renderiza
RiveComponentdentro de un contenedor con ancho y alto fijos. - Controla la máquina de estados. Usa
useStateMachineInputen archivos basados en inputs, oautoBind: truecon los hooks de view model en archivos con Data Binding. - Gestiona el movimiento reducido. Comprueba
prefers-reduced-motiony, cuando su valor seareduce, muestra en su lugar una imagen estática (póster).
Preguntas frecuentes
¿Debo usar @rive-app/react-canvas o @rive-app/react-webgl2?
Usa @rive-app/react-webgl2 en la mayoría de los proyectos; Rive lo recomienda y es compatible con funciones como el difuminado vectorial (vector feathering). Elige @rive-app/react-canvas cuando una página muestre muchas animaciones a la vez y quieras evitar los límites de contextos WebGL, o cuando WebGL no esté disponible. Ambos exponen la misma API.
¿Por qué mi input de Rive es null en React?
useStateMachineInput devuelve null hasta que el archivo carga y la máquina de estados está en ejecución. Protege las llamadas con encadenamiento opcional, pasa el nombre exacto de la máquina de estados tanto a useRive como al hook, y revisa mayúsculas y espacios en los nombres de los inputs. Los archivos convertidos a view models ya no tienen inputs.
¿Rive funciona con React Server Components?
Los componentes de Rive deben ejecutarse en el navegador, así que no pueden ser componentes de servidor. Pon el código de Rive en un componente marcado con "use client" e impórtalo desde un componente de servidor. El servidor renderiza la página que lo rodea mientras el componente de Rive se hidrata y dibuja en el cliente.
¿Puedo usar stateMachines en lugar de stateMachine?
El código antiguo usa la opción en plural stateMachines, que Rive marcó como obsoleta en la v2.41.0 del runtime web en favor de stateMachine en singular. El código antiguo puede seguir funcionando, pero el código nuevo debe usar stateMachine para coincidir con la documentación actual y evitar fallos futuros cuando se eliminen las opciones obsoletas.
Respuestas rápidas de esta guía
Fuentes
- React runtime, Rive
- React runtime: Inputs (legacy), Rive
- Canvas vs WebGL2, Rive
- Web runtime: State machines, Rive
- Runtime sizes, Rive
- Data binding migration guide, Rive
- prefers-reduced-motion, MDN
- Runtimes: Getting started, Rive