DEV Community

Hyperiux
Hyperiux

Posted on

Installing Hyperiux Vault in Next.js: From CLI to a Production-Ready Cursor Effect

Most React projects already have the structural UI covered: navigation, cards, grids, forms, responsive layouts.

The harder part is often the interaction layer — cursor effects, text reveals, scroll sequences, transitions, and other motion that sits between those components.

We can build those effects ourselves with GSAP, Motion, CSS, or WebGL. But when an interaction pattern already exists, starting from reusable source can save implementation time.

Hyperiux Vault provides creative effects for React and Next.js, including cursor interactions, text animations, scroll effects, navigation effects, loaders, page transitions, backgrounds, and WebGL scenes.

In this tutorial, we'll install Phantom Image Trail, a GSAP-powered cursor effect, into a Next.js project.

Then we'll:

  1. initialize Vault
  2. install the effect
  3. inspect the files added to the project
  4. render it on a real page
  5. customize the animation
  6. adjust its accessibility and mobile behavior
  7. verify everything with a production build

Version note: Vault is actively evolving, so record your CLI version before following the tutorial:

npx hyperiux --version

If generated paths or implementation details differ from the examples below, use the files produced by your installed CLI as the source of truth.

How Hyperiux Vault works

Vault uses a source-first approach.

Instead of importing the visual component from a Vault runtime package, the CLI copies the selected effect's source files into your project.

You then import that local code from your application.

That means we can:

  • inspect the animation implementation
  • change GSAP timing and easing
  • change styling
  • modify responsive behavior
  • adapt accessibility behavior
  • maintain the source alongside the rest of the application

There is a tradeoff.

Once the implementation lives in our repository, accessibility, upgrades, compatibility, testing, and performance changes become our responsibility too.

We'll see a practical example of that later in this tutorial.

Prerequisites

The current Vault documentation supports setups including:

Requirement Supported setup
Node.js 18.17+; Node 20+ recommended
React React 18+
Next.js Next.js 14 or 15
Tailwind CSS v3 or v4
Language TypeScript recommended; modern JavaScript supported
Package managers npm, pnpm, yarn, or bun
Project structure App Router, Pages Router, or modern Vite React

For this tutorial we'll use:

  • Next.js App Router
  • TypeScript
  • Tailwind CSS
  • npm

A simplified existing project might look like:

my-app/
├── src/
│   └── app/
│       ├── globals.css
│       ├── layout.tsx
│       └── page.tsx
├── public/
├── package.json
└── tsconfig.json
Enter fullscreen mode Exit fullscreen mode

Using src/ is not required by Vault.

If your project doesn't use it, keep your existing structure.

Before changing an existing project

Vault writes source into our repository, so it is worth creating a clean checkpoint first.

If you're using Git:

git status --short
Enter fullscreen mode Exit fullscreen mode

Commit your current work, or otherwise make sure you can restore the project.

Also record the Vault CLI version:

npx hyperiux --version
Enter fullscreen mode Exit fullscreen mode

After installation we'll inspect the project diff rather than assuming which files changed.

Initialize Hyperiux Vault

From the project root, run:

npx hyperiux init
Enter fullscreen mode Exit fullscreen mode

Vault initializes configuration for the project.

The current CLI structure uses a root-level configuration file named:

hyperiux.json
Enter fullscreen mode Exit fullscreen mode

This configuration tells Vault how the project is structured and where different source files belong.

For example, an aliases section may look similar to:

{
  "aliases": {
    "components": "@/components",
    "effects": "@/components/effects",
    "hooks": "@/hooks",
    "lib": "@/lib"
  }
}
Enter fullscreen mode Exit fullscreen mode

Open the hyperiux.json generated in your project rather than copying an example blindly.

The most important value for this tutorial is:

aliases.effects
Enter fullscreen mode Exit fullscreen mode

That determines where Vault effects are written and helps explain the import path we'll use later.

Preview the Phantom Image Trail installation

Before installing the effect, you can inspect what the CLI plans to add:

npx hyperiux add phantom-image-trail --dry-run
Enter fullscreen mode Exit fullscreen mode

This is particularly useful when you're integrating Vault into an existing codebase.

Now install the effect:

npx hyperiux add phantom-image-trail
Enter fullscreen mode Exit fullscreen mode

phantom-image-trail is the Vault effect we'll use throughout this tutorial.

It uses GSAP for the animation.

After installation, verify that GSAP exists in the project:

npm ls gsap
Enter fullscreen mode Exit fullscreen mode

If it is missing, install it:

npm install gsap
Enter fullscreen mode Exit fullscreen mode

Browse available Vault effects

If you want to see the effects currently available through the CLI, run:

npx hyperiux list
Enter fullscreen mode Exit fullscreen mode

This command shows all available Vault effects.

It is not a list of effects already installed in your application.

For installation verification, we'll instead use:

  • the generated source files
  • the project diff
  • the resolved React import
  • the running application

Those checks tell us much more about whether the integration actually works.

Inspect exactly what changed

After installing the effect, run:

git status --short
git diff
Enter fullscreen mode Exit fullscreen mode

This is useful for any source-first tool.

Rather than assuming the CLI touched only one directory, we can inspect the actual changes made by the version we're using.

Pay attention to:

  • hyperiux.json
  • effect source files
  • package.json
  • lockfiles
  • stylesheets
  • any other modified project files

If something unexpected changed, investigate it before continuing.

What source did Vault add?

The exact generated filenames and directories can vary with project configuration and Vault versions.

For a TypeScript project using src/, the result may resemble:

my-app/
├── hyperiux.json
├── src/
│   ├── app/
│   │   └── page.tsx
│   └── components/
│       └── effects/
│           └── phantom-image-trail/
│               ├── index.tsx
│               ├── useMouse.ts
│               └── createSuspendedRaf.ts
└── package.json
Enter fullscreen mode Exit fullscreen mode

Use the files your CLI actually generated.

The three main pieces have different responsibilities:

  • index contains the component and the GSAP trail animation
  • useMouse tracks pointer movement
  • createSuspendedRaf manages animation-frame work

This is one of the useful parts of Vault's model: if we later want to change pointer behavior, spawning rules, or the animation lifecycle, we know where to look.

Add Phantom Image Trail to the page

Assuming your Vault effects alias resolves to @/components/effects, import the component like this:

import PhantomImageTrail from "@/components/effects/phantom-image-trail";
Enter fullscreen mode Exit fullscreen mode

If your hyperiux.json uses another path, use that instead.

Now update src/app/page.tsx:

import PhantomImageTrail from "@/components/effects/phantom-image-trail";

const images = [
  {
    src: "https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-11.jpg",
    alt: "",
  },
  {
    src: "https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-12.jpg",
    alt: "",
  },
  {
    src: "https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-13.jpg",
    alt: "",
  },
];

export default function Home() {
  return (
    <main className="min-h-screen overflow-hidden bg-[#f8fdfe]">
      <PhantomImageTrail
        images={images}
        enableRotation={true}
        idleSpawn={false}
        idleDelay={300}
        cursorOffsetX={-12}
        cursorOffsetY={-12}
        popOutDuration={0.8}
        fadeOutDuration={0.5}
        idlePopOutMultiplier={2.2}
        idleFadeMultiplier={1.8}
        imageMultiplier={3}
      />
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

With:

idleSpawn={false}
Enter fullscreen mode Exit fullscreen mode

we expect images to appear when the pointer moves rather than spawning while the cursor is idle.

The full-height wrapper is only demo framing. Phantom Image Trail does not require every production page to occupy exactly one viewport.

Fix decorative image semantics

For this example, the trail images are purely decorative.

Normally that would mean:

alt=""
Enter fullscreen mode Exit fullscreen mode

But this is where inspecting the copied Vault source becomes useful.

In the current Phantom Image Trail implementation, the image normalization logic uses a fallback similar to:

alt: image.alt || `Trail ${index + 1}`,
Enter fullscreen mode Exit fullscreen mode

An empty string is falsy, so our intentional:

alt: ""
Enter fullscreen mode Exit fullscreen mode

gets replaced with something such as:

Trail 1
Enter fullscreen mode Exit fullscreen mode

For decorative images, that's not what we want.

Open the generated Phantom Image Trail entry file.

Before

alt: image.alt || `Trail ${index + 1}`,
Enter fullscreen mode Exit fullscreen mode

After

alt: image.alt ?? `Trail ${index + 1}`,
Enter fullscreen mode Exit fullscreen mode

The nullish coalescing operator only falls back for null or undefined.

An intentionally empty string remains empty.

That means:

alt: ""
Enter fullscreen mode Exit fullscreen mode

now correctly produces decorative image semantics.

If your images contain meaningful project or product information, use appropriate alt text instead — and make sure the same information exists independently of the cursor animation.

Run the application

Start Next.js:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Open the URL printed by Next.js, normally:

http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

Move the pointer across the page.

You should see images appear along the pointer path, animate into view, and fade away.

Verify:

  • the component renders
  • pointer movement produces the trail
  • GSAP resolves correctly
  • Tailwind styling is present
  • there are no console errors
  • there are no hydration warnings

A successful CLI command isn't enough. The useful verification is seeing the interaction work inside the real application.

Customize the images

Now let's replace the demo images.

Add:

public/
└── images/
    ├── project-01.jpg
    ├── project-02.jpg
    └── project-03.jpg
Enter fullscreen mode Exit fullscreen mode

Then update the array:

const images = [
  { src: "/images/project-01.jpg", alt: "" },
  { src: "/images/project-02.jpg", alt: "" },
  { src: "/images/project-03.jpg", alt: "" },
];
Enter fullscreen mode Exit fullscreen mode

Because we already adjusted the source normalization, those empty strings remain decorative.

Reduce the trail intensity

Let's make the effect calmer.

Before

<PhantomImageTrail
  images={images}
  enableRotation={true}
  popOutDuration={0.8}
  fadeOutDuration={0.5}
  imageMultiplier={3}
/>
Enter fullscreen mode Exit fullscreen mode

After

<PhantomImageTrail
  images={images}
  enableRotation={false}
  triggerDistance={140}
  popOutDuration={0.55}
  fadeOutDuration={0.35}
  imageMultiplier={2}
/>
Enter fullscreen mode Exit fullscreen mode

triggerDistance controls how far the pointer travels before another image is introduced.

imageMultiplier deserves extra attention.

The implementation effectively uses:

source image count × imageMultiplier
Enter fullscreen mode Exit fullscreen mode

So three source images with:

imageMultiplier={3}
Enter fullscreen mode Exit fullscreen mode

can result in nine trail image elements being available to the animation.

That means imageMultiplier affects performance as well as aesthetics.

Make mobile behavior intentional

Cursor effects are primarily designed around fine-pointer devices.

For production, we generally don't want a decorative desktop cursor trail to become required touch interaction.

The current implementation includes mobile-related behavior such as mobile detection and tap spawning.

The source currently exposes defaults equivalent to:

disableOnMobile = false,
enableMobileTap = true,
Enter fullscreen mode Exit fullscreen mode

That means touch interaction may still spawn decorative trail images.

For this tutorial, we want coarse-pointer users to keep normal touch behavior.

Because these are implementation-level details rather than something we should assume is permanent public API, we'll change the owned source.

Update the defaults:

disableOnMobile = true,
enableMobileTap = false,
Enter fullscreen mode Exit fullscreen mode

Then make the tap handler explicitly respect disableOnMobile.

Before

if (!enableMobileTap || !isMobileRef.current) return;
Enter fullscreen mode Exit fullscreen mode

After

if (
  disableOnMobile ||
  !enableMobileTap ||
  !isMobileRef.current
) {
  return;
}
Enter fullscreen mode Exit fullscreen mode

Also include disableOnMobile in that callback's dependency array where appropriate.

Now tapping on a touch-first device does not spawn decorative trail images.

Make reduced motion actually disable the trail

The effect source also detects:

(prefers-reduced-motion: reduce)
Enter fullscreen mode Exit fullscreen mode

but detection alone isn't enough.

For our final implementation, users requesting reduced motion should not receive the pointer trail.

Find the function responsible for spawning the next image.

If it currently starts with something like:

if (!totalImages) return;
Enter fullscreen mode Exit fullscreen mode

change it to:

if (prefersReducedMotion || !totalImages) return;
Enter fullscreen mode Exit fullscreen mode

Make sure prefersReducedMotion appears in the callback's dependency list.

We can also stop idle spawning immediately:

if (
  prefersReducedMotion ||
  !idleSpawn ||
  (disableOnMobile && isMobileRef.current)
) {
  return;
}
Enter fullscreen mode Exit fullscreen mode

And if your frame loop continues processing pointer animation work, give it an early return too:

if (prefersReducedMotion) {
  return;
}
Enter fullscreen mode Exit fullscreen mode

If the generated implementation contains UI copy saying that the trail continues under reduced motion, remove or update that message because it no longer reflects our behavior.

Our fallback is simple: the page remains usable without the decorative trail.

That's exactly what we want.

Why page.tsx doesn't need "use client"

Phantom Image Trail depends on browser-side behavior such as:

  • pointer movement
  • window.matchMedia
  • animation frames
  • measurements
  • GSAP timelines

So the effect itself belongs behind a Client Component boundary.

The generated implementation already establishes that boundary with:

"use client";
Enter fullscreen mode Exit fullscreen mode

Our page.tsx does not need the same directive.

A Next.js Server Component can render a Client Component.

The boundary begins at the client-marked module.

We also don't need to reach for:

dynamic(..., { ssr: false })
Enter fullscreen mode Exit fullscreen mode

automatically.

Use that pattern when an implementation genuinely cannot participate in server rendering. Don't disable SSR by reflex.

Common problems

The import cannot be resolved

Open:

hyperiux.json
Enter fullscreen mode Exit fullscreen mode

and inspect:

aliases.effects
Enter fullscreen mode Exit fullscreen mode

Then compare it with the directory created by the CLI.

Start with the filesystem, not guesswork.

GSAP cannot be resolved

Run:

npm ls gsap
Enter fullscreen mode Exit fullscreen mode

If GSAP is absent:

npm install gsap
Enter fullscreen mode Exit fullscreen mode

Styles are missing

Vault supports Tailwind CSS v3 and v4.

For Tailwind v3, make sure your content configuration scans the directory where the effect was generated.

Tailwind v4 uses a different source-detection model, so debug the actual v4 setup rather than blindly copying a v3 configuration.

My screen reader announces “Trail 1”

Check the image normalization change.

For decorative images, explicit:

alt: ""
Enter fullscreen mode Exit fullscreen mode

must survive normalization.

Images still spawn when I tap on mobile

Inspect the generated mobile tap handler.

Make sure:

enableMobileTap
Enter fullscreen mode Exit fullscreen mode

is disabled and that your handler also respects:

disableOnMobile
Enter fullscreen mode Exit fullscreen mode

Reduced motion still shows the trail

Confirm that prefersReducedMotion prevents the image-spawning function itself from running.

Showing a reduced-motion notice while continuing the same animation is not the behavior we're targeting.

Performance dropped after replacing the images

Animation cost is only part of the equation.

Large photography also adds:

  • network transfer
  • image decoding
  • memory usage
  • compositing work

Try lowering:

imageMultiplier
Enter fullscreen mode Exit fullscreen mode

and optimize the image files before assuming the GSAP timeline itself is the bottleneck.

Optional CLI troubleshooting

If you want to inspect which commands your installed Vault CLI exposes:

npx hyperiux --help
Enter fullscreen mode Exit fullscreen mode

The Vault documentation also references:

npx hyperiux doctor
Enter fullscreen mode Exit fullscreen mode

for diagnosing project configuration issues.

Because CLI capabilities can change by version, checking --help first keeps the tutorial tied to the software actually installed in your project.

Verify the production build

Development mode isn't the final test for a Next.js animation.

Build the project:

npm run build
Enter fullscreen mode Exit fullscreen mode

This can expose problems such as:

  • TypeScript failures
  • unresolved imports
  • build-time rendering errors
  • dependency problems
  • incorrect client/server boundaries

If your project uses the standard Next.js production script:

npm run start
Enter fullscreen mode Exit fullscreen mode

Test that build instead of judging performance only from npm run dev.

Production checklist

Before shipping Phantom Image Trail:

  • [ ] npm run build succeeds
  • [ ] the trail works with a fine pointer on desktop
  • [ ] coarse-pointer devices keep normal touch behavior
  • [ ] tapping on mobile does not spawn decorative trail images
  • [ ] prefers-reduced-motion: reduce prevents the trail from spawning
  • [ ] decorative images preserve alt=""
  • [ ] meaningful content exists independently of the cursor animation
  • [ ] focus rings and clickable controls remain unobstructed
  • [ ] there are no console or hydration warnings
  • [ ] images are appropriately compressed
  • [ ] performance is tested using the production build
  • [ ] the post-install Git diff contains only changes you intend to keep

Final result

We didn't stop at:

npx hyperiux add phantom-image-trail
Enter fullscreen mode Exit fullscreen mode

We initialized Vault, inspected hyperiux.json, previewed the installation, checked the resulting source files, rendered the GSAP image trail in Next.js, customized it, and changed the copied implementation so its decorative images, mobile behavior, and reduced-motion behavior match the production expectations we're testing against.

That's the practical consequence of source-first components.

The install command gives us a starting point. The code is ours after that.

Browse Hyperiux Vault and the Vault documentation, find an interaction that solves a real design problem, and inspect what it actually does once it enters your repository.

The interesting part starts after the install command.

Top comments (0)