Ir al contenido
charmotion

Cómo usar Rive en React y Next.js

Por el equipo de Charmotion · Actualizado el

Ilustración de portada de la guía: Cómo usar Rive en React y Next.js, con personajes de demostración de Charmotion

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:

PaqueteRenderizadorCuándo usarlo
@rive-app/react-webgl2Rive Renderer sobre WebGL2Opción predeterminada; admite todas las funciones, incluido el vector feathering
@rive-app/react-canvasCanvas2D del navegadorMuchas instancias en una misma página, o donde WebGL no está disponible
@rive-app/react-canvas-liteCanvas2D, con un conjunto reducido de funcionesLa 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);
success: un trigger que se reproduce una vez y luego vuelve al estado de ánimo actual.

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:

  1. Asegúrate de que el componente que importa Rive empiece con "use client".
  2. 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".

  1. Nunca accedas a window, document ni a la instancia de Rive en el nivel superior del módulo. Hazlo dentro de useEffect o 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

  1. Instala el runtime. Ejecuta npm install @rive-app/react-webgl2 (o @rive-app/react-canvas si tienes muchas instancias en una página).
  2. Agrega el archivo. Coloca el .riv en public/ para que se sirva en una URL estable, como /character.riv.
  3. Crea un client component. Empieza el archivo con "use client" y llama a useRive({ src, stateMachine, autoplay: true }).
  4. Renderízalo y define su tamaño. Renderiza RiveComponent dentro de un contenedor con ancho y alto fijos.
  5. Controla la máquina de estados. Usa useStateMachineInput en archivos basados en inputs, o autoBind: true con los hooks de view model en archivos con Data Binding.
  6. Gestiona el movimiento reducido. Comprueba prefers-reduced-motion y, cuando su valor sea reduce, 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

Guías relacionadas