From 33be9f1ca41186e2e8b756b3bfab971ae461da77 Mon Sep 17 00:00:00 2001 From: BFlat Date: Tue, 26 May 2026 19:34:36 +0700 Subject: [PATCH] ## Fix: mobile signup modal freeze (defer Google Sign-In) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Problem:** On mobile Safari, tapping "Sign up to create" on the create pages froze for 5–10s before the signup modal appeared. Desktop was fine. **Cause:** The modal mounts Google's GSI sign-in widget (``), which boots a cross-site iframe to `accounts.google.com`. Mobile Safari's tracking prevention (ITP) throttles that cross-site init for several seconds, and because the widget mounted synchronously when the modal opened, the modal's first paint was blocked behind it. The login/signup pages didn't show this because they mount the widget during a route change, hiding the cost. **Fix:** Defer mounting the real GSI iframe until after first paint (`useEffect` + `requestAnimationFrame`). The custom "Continue with Google" button shows instantly; the actual Google button hydrates a frame later, off the critical path. The container has a fixed height, so there's no layout shift. - Centralized in `GoogleLoginButton`, so the login/signup pages benefit too. - Tradeoff: the Google button becomes clickable ~1 frame later (imperceptible). - No behavior change on desktop. --- .../src/components/auth/GoogleLoginButton.tsx | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/frontend/apps/artcraft-webapp/src/components/auth/GoogleLoginButton.tsx b/frontend/apps/artcraft-webapp/src/components/auth/GoogleLoginButton.tsx index d54b1fbe25..a5c976c5ea 100644 --- a/frontend/apps/artcraft-webapp/src/components/auth/GoogleLoginButton.tsx +++ b/frontend/apps/artcraft-webapp/src/components/auth/GoogleLoginButton.tsx @@ -8,7 +8,7 @@ import { } from "@storyteller/common"; import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useState } from "react"; +import { useEffect, useState } from "react"; interface GoogleLoginButtonProps { onSuccess: (isNewUser: boolean) => void; @@ -25,6 +25,17 @@ export function GoogleLoginButton({ }: GoogleLoginButtonProps) { const [isLoading, setIsLoading] = useState(false); + // Defer mounting Google's GSI widget until after first paint. Its cross-site + // init stalls for several seconds on mobile Safari (ITP throttles + // accounts.google.com), so mounting it inline blocks the surrounding UI (e.g. + // the signup modal) from appearing. The custom visual button below shows + // instantly; the real Google button hydrates a frame later. + const [mountGsi, setMountGsi] = useState(false); + useEffect(() => { + const raf = requestAnimationFrame(() => setMountGsi(true)); + return () => cancelAnimationFrame(raf); + }, []); + const handleGoogleSuccess = async ( credentialResponse: CredentialResponse, ) => { @@ -75,19 +86,23 @@ export function GoogleLoginButton({ )} - {/* Google's actual button - invisible overlay that receives clicks */} -
- onError("Google login failed")} - size="large" - width={400} - text="continue_with" - /> -
+ {/* Google's actual button - invisible overlay that receives clicks. + Mounted after first paint (see mountGsi) so its slow cross-site init + never blocks the surrounding UI from showing. */} + {mountGsi && ( +
+ onError("Google login failed")} + size="large" + width={400} + text="continue_with" + /> +
+ )} ); }