한 번 SDK 스크립트가 로드 된 상황에서 뒤로가기 버튼을 눌러 다시 카메라 페이지로 페이지 이동 시, SDK 카메라가 켜지지 않는다.
✅ 문제는 이미 한번 스크립트가 로드되었기에, 페이지 이동 시 컴포넌트가 새롭게 마운트되지 않으면 스크립트가 다시 로드되지 않아서 SDK 카메라가 제대로 동작되지 않게 된다.
❗️ Next.js에서는
Script태그는 Next.js의 최적화된 스크립트 관리 방식에 따라 한 번 로드된 스크립트를 캐싱 및 재사용하여 페이지 이동 시 리로드되지 않는다.❗️ 페이지 이동 후 스크립트 초기화 동작이 필요한 경우, 기존 스크립트 제거 및 재로드가 필요함
https://stackoverflow.com/questions/73221131/next-js-reload-script-tag
스크립트 로드 시 Next.js
Script태그 대신에, 순수 자바스크립트로document.createElement를 사용해 스크립트를 동적으로 추가하는 방식으로 변경한다.
스크립트의 key를 다르게 하여 페이지 전환이 있을 때마다 고유한 스크립트를 로드하도록 시도
const SDKInitializer = () => {
const [sdkInitStatus, setSdkInitStatus] = useRecoilState(sdkInitState);
const [scriptVersion, setScriptVersion] = useState(Date.now());
// 스크립트 재로드가 필요할 때 호출
const reloadScripts = () => {
setScriptVersion(Date.now());
};
const handleSDKLoad = () => {
setSdkInitStatus((prev) => ({
...prev,
isScriptLoaded: true,
isInitialized: false,
progress: 0,
}));
};
return (
<>
<Script
key={`A-${scriptVersion}`}
src="..."
strategy="afterInteractive"
onLoad={() => console.log('A script loaded')}
/>
<Script
key={`B-${scriptVersion}`}
src="..."
strategy="afterInteractive"
onLoad={() => (
console.log('A script loaded')
() => handleSDKLoad())}
/>
</>
);
export default SDKInitializer;
const SDKProvider = ({ children }: SDKProviderProps) => {
const [sdkInitStatus, setSdkInitStatus] = useRecoilState(sdkInitState);
const pathname = usePathname();
// try-on 관련 페이지에서만 초기화되도록
const shouldInitSDK = useCallback(() => {
return pathname === '/try-on' || pathname === '/try-on-loading';
}, [pathname]);
// 동적으로 스크립트 추가
const loadScript = useCallback((src: string) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = resolve;
script.onerror = reject;
script.id = `sdk-script-${src.split('/').pop()}`;
document.body.appendChild(script);
});
}, []);
// 스크립트 초기화 로직
const initializeSDK = useCallback(async () => {
try {
setSdkInitStatus((prev) => ({
...prev,
isScriptLoaded: false,
isInitialized: false,
progress: 0,
}));
await Promise.all([
loadScript(SDK_SCRIPTS.A),
loadScript(SDK_SCRIPTS.B),
]);
setSdkInitStatus((prev) => ({
...prev,
isScriptLoaded: true,
}));
} catch (error) {
console.error('Failed to load SDK scripts:', error);
setSdkInitStatus((prev) => ({
...prev,
error: error instanceof Error ? error.message : 'An unknown error occurred',
}));
}
}, [loadScript, setSdkInitStatus]);
useEffect(() => {
if (shouldInitSDK()) {
initializeSDK();
}
// 언마운트 시 스크립트 제거로 메노리 누수 방지
return () => {
if (!shouldInitSDK()) {
const scripts = document.querySelectorAll('script[id^="sdk-script-"]');
scripts.forEach((script) => script.remove());
setSdkInitStatus((prev) => ({
...prev,
isScriptLoaded: false,
isInitialized: false,
progress: 0,
error: null,
}));
}
};
}, [pathname, shouldInitSDK, initializeSDK]);
return <>{children}</>;
};
export default SDKProvider;
'use client';
import { CustomToastContainer, SDKProvider } from '@/components';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { PropsWithChildren, useState } from 'react';
import { RecoilRoot } from 'recoil';
import 'react-toastify/dist/ReactToastify.css';
const Providers = ({ children }: PropsWithChildren) => {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
}),
);
return (
<RecoilRoot>
<SDKProvider>
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
<CustomToastContainer />
{children}
</QueryClientProvider>
</SDKProvider>
</RecoilRoot>
);
};
export default Providers;