fix(www): resolve globe component memory leak and resizing issues (#46025)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES

## What kind of change does this PR introduce?

Bug fix

## What is the current behavior?

Resolves #46026

1. **Memory Leak**: The `<Globe />` component in the `www` app creates a
new `debouncedResize` event listener every time the theme changes, but
in the `useEffect` cleanup function, it attempts to remove `onResize`
instead. Because these are two different function references in memory,
the listener is never removed, causing a memory leak that grows with
theme toggling.
2. **Resolution Scaling**: The WebGL `cobe` globe instance is not
notified of width changes dynamically. When a user resizes their browser
window, the `<canvas>` resizes via CSS, but the internal 3D rendering
stretches and becomes distorted because `state.width` and `state.height`
are not updated inside the `onRender` loop.
3. **Local Dev Rendering**: Due to async CSS loading in Next.js
development environments, `offsetWidth` evaluates to 0 on initial mount,
causing the globe to render invisibly until manually resized.

## What is the new behavior?

1. **Memory Leak Fixed**: Switched from a global
`window.addEventListener('resize')` to a `ResizeObserver` attached
directly to the canvas container, properly disconnecting it on unmount.
2. **Resolution Scaling Fixed**: Added `state.width` and `state.height`
updates directly inside the `onRender` callback. The globe now maintains
its aspect ratio and high resolution dynamically as the user resizes the
browser.
3. **Local Dev Rendering Fixed**: The `ResizeObserver` correctly
triggers a size recalculation exactly when the local Next.js CSS
finishes injecting, allowing the globe to render immediately in local
dev.
4. **Safe Timeout**: Wrapped the initial opacity update in a null check
and correctly store/clear the `timeoutId` during unmount.

## Additional context

## Before

https://github.com/user-attachments/assets/813e077d-bcf4-4584-b3da-46c01b6af001


https://github.com/user-attachments/assets/73cef575-27e3-44d2-a191-fd192dec3ef9

## After

https://github.com/user-attachments/assets/cd68c66d-6a35-43d0-971f-04627ef532fb

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Globe now adapts instantly to container size changes for smoother,
consistent rendering.
* Improved lifecycle cleanup to reliably stop background tasks and clear
timers, reducing memory leaks.
* Enhanced stability and performance for longer-running sessions of the
Globe component.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46025?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
hasithasandun authored and GitHub committed 2026-07-02 11:31:37 +02:00
1 parent 3fcf980b0a
commit 450dc56cbe
1 file changed
+30 -14
+30 -14
View File
@@ -1,23 +1,27 @@
import createGlobe from 'cobe'
import { debounce } from 'lib/helpers'
import { useTheme } from 'next-themes'
import { useCallback, useEffect, useRef } from 'react'
import { useEffect, useRef } from 'react'
const Globe = () => {
const { resolvedTheme } = useTheme()
const canvasRef = useRef<any | null>(null)
let rotation: number = 0
let width: number = 0
const onResize = useCallback(
() => canvasRef.current && (width = canvasRef.current.offsetWidth),
[resolvedTheme]
)
useEffect(() => {
const debouncedResize = debounce(onResize, 10)
window.addEventListener('resize', debouncedResize)
onResize()
let rotation = 0
let width = 0
let previousWidth = 0
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
width = Math.round(entry.contentRect.width)
}
})
if (canvasRef.current) {
resizeObserver.observe(canvasRef.current)
width = Math.round(canvasRef.current.getBoundingClientRect().width)
}
const cobe = createGlobe(canvasRef.current, {
devicePixelRatio: 2,
width: width * 2,
@@ -52,11 +56,23 @@ const Globe = () => {
onRender: (state) => {
state.phi = rotation
rotation += 0.0025
if (width !== previousWidth) {
state.width = width * 2
state.height = width * 2
previousWidth = width
}
},
})
setTimeout(() => (canvasRef.current.style.opacity = '0.8'), 10)
const timeoutId = setTimeout(() => {
if (canvasRef.current) {
canvasRef.current.style.opacity = '0.8'
}
}, 10)
return () => {
window.removeEventListener('resize', onResize)
resizeObserver.disconnect()
clearTimeout(timeoutId)
cobe.destroy()
}
}, [resolvedTheme])