SSR & Hydration
viewmotion is designed to work seamlessly in both static and server-rendered environments.
How it works
viewmotion’s animation logic runs entirely on the client. Server-rendered HTML only
contains the data-motion attributes — no JavaScript is executed server-side.
When the page loads in the browser, initMotion() creates an IntersectionObserver that
watches all elements with data-motion attributes. Animations trigger as elements
enter the viewport.
Preventing flash of unstyled content (FOUC)
viewmotion’s CSS hides elements with data-motion until they’re animated in. This means
there’s no visible “flash” — elements stay hidden until the observer fires.
/* viewmotion's internal CSS — you don't need to add this */
[data-motion] {
opacity: 0;
} Static site generators
With Astro (static mode), Gatsby, or any SSG, the motion() helper generates
data-motion attributes at build time. The HTML ships with the correct attributes,
and initMotion() hooks them up in the browser. No special configuration is needed
— it just works.
SSR frameworks
For frameworks with server rendering, make sure to call initMotion() only on the client:
Next.js (App Router)
"use client";
import { useEffect } from "react";
import { initMotion } from "viewmotion";
export default function MotionProvider({ children }) {
useEffect(() => {
initMotion();
}, []);
return <>{children}</>;
} Nuxt
// plugins/viewmotion.client.ts
import { initMotion } from "viewmotion";
export default defineNuxtPlugin(() => {
initMotion();
}); SvelteKit
<!-- +layout.svelte -->
<script>
import { onMount } from "svelte";
import { initMotion } from "viewmotion";
onMount(() => {
initMotion();
});
</script>
<slot /> Key points
-
motion()andstagger()are safe to run on the server — they only return data attributes -
initMotion()must run on the client (it usesIntersectionObserver) - No FOUC — elements are hidden via CSS until animated
- Framework adapter packages handle SSR automatically