Rive no Vue 3 e Nuxt: configuração, Data Binding e SSR
Por equipe Charmotion · Atualizado em

O Rive não tem runtime oficial para Vue, então apps em Vue 3 e Nuxt usam diretamente o runtime web @rive-app/canvas. Crie a instância do Rive no onMounted com autoBind: true, defina as propriedades do view model a partir de um watcher e chame cleanup() no onUnmounted. No Nuxt, renderize o componente só no cliente, porque o canvas e o WASM precisam de um navegador.
Principais pontos
- Não existe pacote do Rive para Vue;
@rive-app/canvasou@rive-app/webgl2funciona em qualquer componente Vue 3. - Crie a instância no
onMounted, nunca no nível superior dosetup, e mantenha-a fora deref(). - Com
autoBind: true, definar.viewModelInstance.number("mood").valuee chame.trigger("success").trigger(). - Chame
r.cleanup()noonUnmountedpara liberar os objetos WASM. - No Nuxt, use um componente
.client.vueou<ClientOnly>para evitar erros no servidor.
Existe um pacote oficial do Rive para Vue?
Não. O Rive mantém runtimes oficiais para web (JavaScript), React, plataformas Apple, Android, Flutter e React Native, entre outros. A documentação lista quatro runtimes da comunidade (Angular, C#, Qt e RiveCMP para Compose Multiplatform), e nenhum deles é para Vue. Uma página de ajuda antiga do Rive, que orientava desenvolvedores Vue a usar o runtime web, foi removida, mas a recomendação continua valendo.
Isso é uma lacuna menor do que parece. O runtime web não depende de framework: ele precisa de um elemento <canvas>, um arquivo .riv e o nome de uma state machine. O Vue oferece exatamente os hooks necessários para fornecer isso no momento certo.
| Necessidade | Pacote | Observações |
|---|---|---|
| Rive no Vue ou no Nuxt | @rive-app/canvas | Renderizador de canvas 2D; sem limite de contextos WebGL |
| Rive com efeitos avançados | @rive-app/webgl2 | Mesma API, Rive Renderer sobre WebGL2 |
| Lottie no Vue | @lottiefiles/dotlottie-vue | Componente oficial da LottieFiles |
| Renderização no servidor do Nuxt | <ClientOnly> ou .client.vue | Canvas e WASM só funcionam no navegador |
O Rive descreve o @rive-app/canvas como adequado para páginas com muitos canvas animados, porque ele não está sujeito ao limite de contextos WebGL do navegador. Escolha @rive-app/webgl2 se o seu arquivo usa recursos que exigem o Rive Renderer; o código abaixo é idêntico, exceto pelo import.
Como carregar um .riv no onMounted e limpá-lo no onUnmounted?
Instale o runtime com npm install @rive-app/canvas e coloque o arquivo em public/, que o Vite e o Nuxt servem a partir da raiz do site. Depois, crie um componente que controla uma instância do Rive:
<script setup>
import { watch, onMounted, onUnmounted, useTemplateRef } from "vue";
import { Rive, Layout, Fit } from "@rive-app/canvas";
const props = defineProps({ mood: { type: Number, default: 0 } });
const canvas = useTemplateRef("canvas");
let r = null; // plain variable: don't wrap the instance in ref()
function setMood(value) {
const prop = r?.viewModelInstance?.number("mood");
if (prop) prop.value = value;
}
function fire(name) {
r?.viewModelInstance?.trigger(name)?.trigger();
}
onMounted(() => {
r = new Rive({
src: "/character.riv",
canvas: canvas.value,
stateMachine: "CharacterViewModelMachine",
autoplay: true,
autoBind: true,
layout: new Layout({ fit: Fit.Contain }),
onLoad: () => {
r.resizeDrawingSurfaceToCanvas();
setMood(props.mood);
},
});
});
watch(() => props.mood, setMood);
onUnmounted(() => r?.cleanup());
defineExpose({ fire });
</script>
<template>
<canvas ref="canvas" width="320" height="320"
role="img" aria-label="Animated mascot" />
</template>
O onMounted do Vue roda depois que o DOM do componente existe, então a ref do canvas já está definida. useTemplateRef exige Vue 3.5; em versões anteriores, use const canvas = ref(null). Guarde a instância do Rive em uma variável simples ou em shallowRef: um proxy reativo profundo em volta de um objeto baseado em WASM adiciona overhead e pode quebrar verificações de identidade dentro do runtime.
cleanup() importa mais do que parece. A documentação do Rive diz que ele remove os objetos C++ de baixo nível criados durante a vida da instância. Sem ele, cada troca de rota que remonta o mascote vaza memória.
Como trocar o humor e disparar triggers a partir do Vue?
O componente acima já faz as duas coisas. O watch em props.mood grava o novo valor no view model vinculado, e o método exposto fire() toca uma reação única. Um componente pai o controla como qualquer outro componente:
<script setup>
import { ref, useTemplateRef } from "vue";
import CharacterRive from "./CharacterRive.vue";
const mascot = useTemplateRef("mascot");
const saving = ref(false);
async function save() {
saving.value = true; // mood 5: loading
try {
await api.save();
mascot.value.fire("success");
} catch {
mascot.value.fire("error");
} finally {
saving.value = false; // back to mood 0: idle
}
}
</script>
<template>
<CharacterRive ref="mascot" :mood="saving ? 5 : 0" />
<button @click="save">Save</button>
</template>

Mantenha o estado do app como fonte da verdade e derive o humor dele, como faz o binding :mood. Triggers são eventos, não estado, então passam por um método, e não por uma prop.
Se o arquivo usa o sistema antigo de inputs em vez de view models, o mesmo padrão funciona com r.stateMachineInputs("CharacterStateMachine"): encontre o input pelo nome, defina .value em um número e chame .fire() em um trigger. O guia de state machine explica os dois sistemas.
Como usar o Rive no Nuxt sem erros de SSR?
O Nuxt renderiza as páginas primeiro no servidor, onde não há window, nem canvas, nem carregador de WebAssembly para o Rive. O onMounted nunca roda no servidor, então o componente acima já é praticamente seguro. O risco que resta é o próprio import: se um módulo acessa globais do navegador ao ser importado, o bundle do servidor lança window is not defined.
O Nuxt oferece duas formas de manter o componente fora da renderização no servidor:
- Nomeie o arquivo como
CharacterRive.client.vue. Assim o Nuxt o renderiza só no cliente, e ele fica fora da renderização no servidor. - Envolva-o em
<ClientOnly>. O slot é renderizado só no navegador, e o slot#fallbacké renderizado no servidor.
<ClientOnly>
<CharacterRive :mood="2" />
<template #fallback>
<img src="/character-poster.png" width="320" height="320" alt="">
</template>
</ClientOnly>
A imagem de fallback mantém o layout estável e dá algo para crawlers e conexões lentas verem. Se preferir manter um único componente universal, mova o import para dentro do hook: const { Rive } = await import("@rive-app/canvas") dentro de um onMounted assíncrono. Registre o onUnmounted de forma síncrona no setup, não depois do await, senão o Vue não consegue associá-lo ao componente.
Como hospedar o rive.wasm por conta própria no Vite ou no Nuxt?
Por padrão, o runtime web baixa o rive.wasm do unpkg na primeira vez que você cria uma instância. É prático, mas adiciona uma requisição de terceiros e falha em ambientes offline ou restritos. A documentação do Rive mostra como apontar o runtime para a sua própria cópia com RuntimeLoader.setWasmUrl() e reforça que a versão do arquivo wasm precisa ser a mesma do pacote @rive-app instalado.
Com o Vite (que o Nuxt usa), o sufixo ?url copia o arquivo para o build e retorna a URL com hash, então a versão sempre corresponde ao package.json:
import { RuntimeLoader } from "@rive-app/canvas";
import riveWasmUrl from "@rive-app/canvas/rive.wasm?url";
// Run once, before the first new Rive(...)
RuntimeLoader.setWasmUrl(riveWasmUrl);
Coloque isso em um pequeno módulo importado pelo componente do Rive ou em um plugin de cliente do Nuxt (plugins/rive.client.js). Como a URL vem do pacote instalado, atualizar o @rive-app/canvas atualiza o wasm automaticamente. Não copie o rive.wasm para public/ manualmente: a próxima atualização do pacote deixaria os dois dessincronizados.
Hospedar por conta própria também permite pré-carregar o arquivo nas páginas em que o mascote aparece na primeira dobra, com um <link rel="preload"> para a URL do wasm, para que ele seja baixado em paralelo com o seu JavaScript.
Rive ou Lottie para um mascote no Vue?
Os dois funcionam no Vue. A escolha depende de o seu código precisar ou não dizer à animação o que fazer em seguida.
- Lottie é indicado para clipes lineares: um check de sucesso, uma ilustração de estado vazio, um spinner. A LottieFiles publica o
@lottiefiles/dotlottie-vue, que renderiza<DotLottieVue autoplay loop src="…" />e toca arquivos .lottie e .json. Trocar de humor significa trocar de arquivo ou de segmento. - Rive é indicado para personagens com estados. Um único .riv contém todos os loops e reações, a state machine cuida das transições, e o seu componente só grava um número ou dispara um trigger.
Se você ainda não tem um arquivo Rive, o Charmotion cria um a partir da imagem de um personagem ou logo em poucos minutos: 17 animações em um único .riv com um view model Character (um número mood de 0 a 9 para idle, happy, wave, thinking, sad, loading, listening, talking, sleeping e attention, além dos triggers success, error, yes, no, enter e exit). O arquivo também mantém os inputs legados da CharacterStateMachine, e o conjunto inclui exportações em MP4, GIF e WebM e MOV com fundo transparente para os lugares onde um canvas não cabe.

Como adicionar um personagem Rive a um app Vue 3 ou Nuxt
- Instale o runtime web. Execute
npm install @rive-app/canvase coloque o arquivo .riv empublic/. - Crie o componente. Adicione um canvas com uma template ref e crie
new Rive({ ..., stateMachine: "CharacterViewModelMachine", autoBind: true })noonMounted. - Vincule o humor. Observe a prop
moode definar.viewModelInstance.number("mood").value, também uma vez noonLoad. - Exponha os triggers. Exponha um método que chame
r.viewModelInstance.trigger(name).trigger()para success e error. - Faça a limpeza. Chame
r.cleanup()emonUnmounted. - Lide com o SSR do Nuxt. Nomeie o arquivo como
.client.vueou envolva-o em<ClientOnly>com uma imagem de pôster como fallback.
Perguntas frequentes
Posso usar @rive-app/react-canvas em um app Vue?
Não. O pacote React depende dos hooks e da renderização do React. No Vue, use @rive-app/canvas ou @rive-app/webgl2, os runtimes web independentes de framework que o próprio pacote React encapsula. A API para carregar arquivos, vincular view models e fazer a limpeza é a mesma documentada nas páginas do runtime web do Rive.
Por que o Nuxt diz que window is not defined quando importo o Rive?
O import foi executado durante a renderização no servidor, onde os globais do navegador não existem. Renomeie o componente para CharacterRive.client.vue para que o Nuxt o renderize só no navegador, envolva-o em <ClientOnly> ou importe @rive-app/canvas dinamicamente dentro de onMounted. Qualquer uma das três opções mantém o runtime fora do servidor.
A instância do Rive deve ficar em um ref?
Mantenha-a em uma variável comum ou em um shallowRef. Um ref() profundo envolve a instância em um proxy reativo, o que adiciona overhead e pode interferir nos objetos baseados em WASM dentro dela. Seu template nunca precisa ler a instância diretamente, então ela não precisa ser reativa.
O watcher de mood roda antes de o arquivo carregar?
Pode rodar, e é por isso que o setter verifica se viewModelInstance existe antes de escrever. Em seguida, o callback onLoad aplica o valor atual da prop assim que o arquivo fica pronto, então o primeiro humor nunca se perde. Depois disso, cada mudança de prop vai direto para o view model vinculado.
Respostas rápidas deste guia
Fontes
- Web (JS) runtime, Rive
- Web runtime: Data binding, Rive
- Preloading WASM, Rive
- Canvas vs WebGL2, Rive
- Lifecycle hooks, Vue.js
- Template refs, Vue.js
- <ClientOnly> component, Nuxt
- dotlottie-web: players for web, React, Vue, Svelte and web components, LottieFiles on GitHub