3.x 업뎃 후 로딩바 진행 불가

이 글의 성격은 무엇인가요?

질문 / 문제 해결

내용을 설명해주세요

appName : three-body-problem
Next JS에서 3.x 버전으로 latest 업데이트 했습니다.
그런데 로딩에서 넘어가질 않네요.
제가 만든 로딩 예시 코드는 아래와 같습니다.

관련코드도 계속 고쳐라고 해봤으나 로딩바가 넘어가질 않습니다.

“use client”;

import “../utils/apps-in-toss-bridge”;
import React, { useState, useEffect, useRef } from “react”;
import {
markHomeAsVisited,
markInitialLoadComplete,
} from “../utils/initial-load”;
import { initBGM } from “../utils/sound”;

const ASSETS_TO_PRELOAD: string = [“/loading.gif”];

interface InitialAssetLoaderProps {
durationMs?: number;
/**

  • loading.gif 이미지 테두리 라운드 크기
    • Tailwind 클래스 (예: “rounded-2xl”, “rounded-3xl”, “rounded-full”)
    • 또는 CSS 크기 값 (예: “24px”, “16px”, “2rem”)
      */
      imageRounding?: string;
      onComplete?: () => void;
      }

export default function InitialAssetLoader({
durationMs = 2000,
imageRounding = “rounded-3xl”,
onComplete,
}: InitialAssetLoaderProps = {}) {
const [progress, setProgress] = useState(0);
const [isLoaded, setIsLoaded] = useState(false);
const [isDismissed, setIsDismissed] = useState(false);

// Store latest onComplete in a ref so changes to onComplete don’t restart the loader effect
const onCompleteRef = useRef(onComplete);
useEffect(() => {
onCompleteRef.current = onComplete;
}, [onComplete]);

// Determine if imageRounding is a Tailwind class or a direct CSS style
const isTailwindClass = imageRounding.startsWith(“rounded”);
const roundedClass = isTailwindClass ? imageRounding : “”;
const borderRadiusStyle = isTailwindClass ? undefined : imageRounding;

useEffect(() => {
markHomeAsVisited();
initBGM();
}, );

useEffect(() => {
let isCancelled = false;
let completedCount = 0;
const totalAssets = ASSETS_TO_PRELOAD.length;
let isPreloadFinished = totalAssets === 0;

const checkComplete = () => {
  completedCount++;
  if (completedCount >= totalAssets) {
    isPreloadFinished = true;
  }
};

// Preload image assets safely
if (typeof window !== "undefined") {
  ASSETS_TO_PRELOAD.forEach((src) => {
    try {
      const img = new Image();
      let handled = false;
      const onDone = () => {
        if (handled) return;
        handled = true;
        checkComplete();
      };

      img.onload = onDone;
      img.onerror = onDone;
      img.src = src;

      if (img.complete) {
        onDone();
      }
    } catch {
      checkComplete();
    }
  });
}

const startTime =
  typeof performance !== "undefined" ? performance.now() : Date.now();
let animationFrameId: number;

const tick = () => {
  if (isCancelled) return;

  const now =
    typeof performance !== "undefined" ? performance.now() : Date.now();
  const elapsed = now - startTime;
  const timeRatio = Math.min(1, elapsed / durationMs);

  let currentProgress: number;
  if (isPreloadFinished || elapsed >= durationMs) {
    currentProgress = Math.floor(timeRatio * 100);
  } else {
    currentProgress = Math.min(95, Math.floor(timeRatio * 95));
  }

  if (elapsed >= durationMs) {
    setProgress(100);
    setTimeout(() => {
      if (!isCancelled) {
        setIsLoaded(true);
        markInitialLoadComplete();
        setTimeout(() => {
          if (!isCancelled) {
            setIsDismissed(true);
            if (onCompleteRef.current) {
              onCompleteRef.current();
            }
          }
        }, 500);
      }
    }, 300);
  } else {
    setProgress((prev) => Math.max(prev, Math.max(1, currentProgress)));
    animationFrameId = requestAnimationFrame(tick);
  }
};

animationFrameId = requestAnimationFrame(tick);

return () => {
  isCancelled = true;
  if (typeof cancelAnimationFrame !== "undefined" && animationFrameId) {
    cancelAnimationFrame(animationFrameId);
  }
};

}, [durationMs]);

if (isDismissed) return null;

return (
<div
suppressHydrationWarning
onClick={initBGM}
onTouchStart={initBGM}
className={fixed inset-0 z-[99999] flex flex-col items-center justify-center bg-gradient-to-b from-black via-slate-950 to-black text-slate-100 select-none p-6 transition-opacity duration-500 ease-out ${ isLoaded ? "opacity-0 pointer-events-none" : "opacity-100" }}
>
{/* Subdued cosmic background glows */}


  {/* Dim background stars */}
  <div className="absolute top-12 left-10 text-sky-400/20 text-3xl animate-pulse pointer-events-none">
    ✦
  </div>
  <div className="absolute bottom-16 right-12 text-indigo-400/20 text-4xl animate-pulse pointer-events-none">
    ✦
  </div>
  <div className="absolute top-1/3 right-10 text-slate-500/20 text-2xl animate-bounce pointer-events-none">
    ★
  </div>

  {/* Main Loader Content */}
  <div className="relative z-10 w-full max-w-md sm:max-w-lg flex flex-col items-center space-y-6 text-center">
    {/* Large Loading GIF with customizable rounded corners */}
    <div className="relative flex items-center justify-center w-full">
      <img
        src="/loading.gif"
        alt="Loading"
        className={`w-[85vw] sm:w-[380px] max-w-[440px] h-auto object-cover select-none overflow-hidden filter drop-shadow-[0_10px_30px_rgba(0,0,0,0.9)] transition-transform duration-300 ${roundedClass}`}
        style={{ borderRadius: borderRadiusStyle }}
      />
    </div>

    {/* Title and Status Text */}
    <div className="space-y-2">
      <h2 className="text-2xl sm:text-3xl font-black tracking-tight text-transparent bg-clip-text bg-gradient-to-r from-slate-100 via-sky-200 to-slate-300 drop-shadow">
        Three Body Problem
      </h2>
      <p className="text-xs sm:text-sm font-semibold text-slate-500 tracking-wide">
        우주를 다운 받고 있어요...
      </p>
    </div>

    {/* Ultra Dark Progress Bar Container */}
    <div className="w-full max-w-sm space-y-2 pt-2">
      <div className="w-full bg-black/90 rounded-full h-3 p-0.5 border border-slate-800 shadow-inner relative overflow-hidden">
        {/* Animated Progress Fill */}
        <div
          className="bg-gradient-to-r from-sky-600 via-cyan-500 to-blue-600 h-full rounded-full transition-all duration-200 ease-out relative shadow-[0_0_10px_rgba(56,189,248,0.3)]"
          style={{ width: `${progress}%` }}
        >
          <div className="absolute inset-0 bg-white/20 rounded-full animate-pulse" />
        </div>
      </div>

      <div className="flex items-center justify-between text-xs font-bold text-slate-400 px-1">
        <span>삼체 세계 진입 중</span>
        <span className="text-sky-400">{progress}%</span>
      </div>
    </div>
  </div>
</div>

);
}

안녕하세요 :slight_smile:
브라우저에서 구동시켰을때 콘솔에 어떤 에러가 나는지 확인이 가능할까요 ?

@conachaju 님 안녕하세요

V2와 V3 번들을 모두 확인해봤는데, document에서 asset을 참조하는 경로가 /_next./_next로 바뀌어 있고, 빌드 config에도 차이가 있는 것으로 확인돼요.

혹시 next.config.tsassetPrefix: "."가 들어가 있는지 확인해 주실 수 있을까요?
만약 아니라면, post-build 같은 스크립트에서 빌드 결과물의 에셋 경로를 상대 경로로 바꾸고 있는 건 아닌지도 확인 부탁드려요.

말씀하신대로 하니 되네요 감사합니다.

3.x 부터는 샌드박스를 사용할 수 없나요?

웹에서 확인을 하긴 하지만, 미세한 UI 수정은 샌드박스에서 해야할텐데…

넵 가이드에 안내드린대로 SDK 3.X부터는 샌드박스 사용이 불가합니다