From static signup to interactive scene
WebGL is the browser's JavaScript API for rendering 3D graphics, and it enables experiences that static images simply cannot match. That's what made it possible to transform what would have been a routine conference registration page into the immersive Next.js Conf signup experience. This article breaks down how to recreate the centerpiece of that design—an interactive light beam that bounces off objects and splits into a rainbow—using open-source WebGL tooling, including a new performance tool from Vercel engineers.
Starting with a concept
The design process began with DALL-E prompts exploring 3D rendering, triangles, pyramids, and light beams. The generated images helped focus the discussion on rainbows as a metaphor for the visible spectrum and the full potential of the web. The mood board that emerged centered on prisms, beams of light, and rainbows as vivid visual anchors.
The team also wanted the experience to be both interactive and accessible. The core idea became: let users control a light source that reveals hidden particles in a scene, bounces off objects, hits a prism, and produces a rainbow.
Building the prototype
The proof of concept relies on two packages from Poimandres, a developer collective:
- react-three-fiber: A library for using three.js within React components
- drei: A collection of convenience helpers for common three.js patterns
With react-three-fiber, developers interact with 3D objects using familiar React component patterns. The library has no limits compared to raw three.js, exposes event props like onRayHit that work like React's native events, and leverages React's scheduler to outperform raw three.js at scale.
Simulating a light beam
The first step toward the final experience was building a 2D "light beam" that follows the cursor and reflects off objects. The sandbox accomplishes this with:
- An
Appcomponent that creates a canvas with anorthographiccamera and dark background - A
Scenecomponent usinguseLayoutEffectanduseFrame(a customreact-three-fiberhook that runs on every drawn frame) to track the cursor BlockandTrianglecomponents withonRayOverandonRayOutevent handlers- Two
.pngtextures for the light beam and the glow at intersection points—letting the browser reposition the images rather than demanding extra WebGL rendering work
Adding the rainbow
To generate the rainbow exiting the prism, the team drew on two Shadertoy shaders: Alan Zucconi's performant visible color spectrum and Juliapoo's iridescence effect for a flowing appearance. Combining both produced an iridescent rainbow effect.
Elevating the visuals
Postprocessing applies filters and effects to the render output before it reaches the screen, and it's considered essential for three.js projects. This project used two libraries:
- react-postprocessing: Another Poimandres package that replaces the three.js
EffectComposermethod with a single merged effect, minimizing render operations - screen-space-reflections: Captures existing screen data to create reflections
Three effects were applied:
Bloom
Bloom places light fringes around the brightest areas, mimicking how real camera lenses react to intense light by distributing it across the image. In this scene, it brings extra life to the bright parts of the light refracting through the prism.
Color Lookup Tables
LUTs filter an original color set into a new range across the whole rendered image. Here, the table was used to give the scene a more blue, cinematic look. If custom tuning isn't needed, pre-made LUTs are available from resources like IWLTBAP.
Screen Space Reflections
These reflections made the scene more dynamic by having the environment reflect within the prism. The effect is calculated through raymarching, where every reflectable pixel is processed step-by-step to find the best reflection point. Because it's resource intensive, the reflections were limited to the prism only and disabled entirely on mobile. The shader reflects the rainbow inside the prism, and also mirrors the box objects and light ray when positioned appropriately.
Assembling the scene
With the core parts in place—a light ray, objects to bounce off, a prism, and a rainbow—the final assembly added a beveled prism model from drei for extra detail.
Managing performance
The visually rich render comes at a cost. Older devices began struggling, and the typical approach of watching GPU utilization proved unreliable, since devices calculate GPU usage inconsistently, causing under- or over-rendering.
A new performance monitor
The alternative was to monitor the final rendered result directly. drei now includes a <PerformanceMonitor /> component that watches frames per second and compares them against a developer-defined acceptable range. When FPS drops too low, the app eases render characteristics to reduce workload; when FPS permits, the rendering improves. This adaptive approach avoids GPU sniffing and removes the burden of choosing quality levels from users.
Putting it all together
The demo we built shows how a few focused WebGL techniques can turn a static registration page into something people actually want to interact with. But the same pattern applies beyond this one use case: the combination of a fast React renderer, a persistent WebGL context, and event handlers that pass data from the DOM into the GPU works for product configurators, data visualizations, and landing pages alike.
Key decisions that shaped the experience
Three design choices drove the architecture:
- Keeping the WebGL layer separate from the React tree. The canvas lives outside the component hierarchy and only receives updates through imperative calls. That avoids re-renders on every animation frame and keeps the React side cheap to update.
- Using a fixed set of shaders. Instead of compiling new programs for each visual state, the demo precompiles a small pool of shaders and switches between them via uniforms. That keeps the GPU busy and the JavaScript idle.
- Treating user interaction as a stream of coordinates. Mouse and touch events are normalized to clip space and written directly into a uniform buffer, so the pointer position is always available to the vertex shader without crossing the JS/WebGL boundary more than necessary.
Where to go from here
If you want to extend the project, a natural next step is adding a second game mode or a leaderboard. Both would slot into the existing structure: the game state logic lives in a custom hook, and the rendering pipeline only needs a new uniform or a slightly different fragment shader to reflect the change.
You can see the full experience—including all three playable games—on the registration page. The source for the mood board and interactive scenes also includes work from outside contributors who helped shape the visual direction.
¹ We'd like to thank contributors to our mood board, including artists like Davo Galavotti.



