버튼을 눌러 페이지를 이동할 때, 데이터가 준비되기 전까지 로딩 스피너를 보여주는 것은 UX 관점에서 매우 중요합니다. 하지만 아래와 같은 패턴으로 구현했을 때, 화면 전환 후에도 스피너가 바로 보이지 않고 약간의 지연이 발생하는 문제가 있었습니다.
버튼을 눌러 페이지를 이동할 때, 데이터가 준비되기 전까지의 로직 중, 스캘리톤 UI를 보여줄지, 아니면 로딩 UI를 통해 화면을 이동중인 것을 보여줄지에 대한 고민을 많이 했었다.
일단 유지보수 측면과 잘못된 스켈레톤은 UX를 어색하게 만들 수 있을 것 같다는 판단이 생겨 조금은 정적이고 페이지를 한번 더 이동해야하지만 로딩UI를 따로 보여주기로 결정하였다.
const CheckWinnerClient = () => {
const [matchingList, setMatchingList] = useState<MatchingData[] | null>(null)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const fetchMatchingRoom = async () => {
try {
setIsLoading(true)
const data = await usingRoomUserList()
setMatchingList(data)
} catch (err) {
console.error(err)
} finally {
setIsLoading(false)
}
}
fetchMatchingRoom()
}, [])
if (isLoading) {
return <LoadingSpinner />
}
return (/* ... 당첨자 UI ... */)
}
if (isLoading) return <LoadingSpinner />
를 사용했는데, 화면을 이동해도 스피너가 바로 보이지 않았다. 프로젝트를 계속 진행하다보니 이번에도 SSR과 CSR간의 문제이지 않을까? 라는 생각이 들었다.
조금 더 찾아보고 알아보니 SSR에서 CSR로 전환할 때, 클라이언트는 JS 번들을 다운로드하고 하이드레이션 하는 과정이 필요하기 때문에 발생하는 지연이었다.
loading.tsx
사용// app/check-winner/loading.tsx
export default function Loading() {
return (
<div className="w-full h-screen flex justify-center items-center bg-white">
<div className="flex flex-col items-center">
<div className="w-12 h-12 border-4 border-gray-200 rounded-full border-t-blue-500 animate-spin" />
<p className="mt-4 text-gray-700 font-semibold">로딩 중…</p>
</div>
</div>
)
}
Next.js의 loading.tsx 기반 fallback 처리를 도입을 하였다.
CSR 전환 중 클라이언트 상태가 초기화되기 전에 일시적으로 로딩 상태를 표시함으로써 사용자에게 끊김 없는 인증 유지 흐름과 자연스러운 UX 제공하였다.
문제 발생 시, SSR,CSR 간의 문제일 것 같았는데 얼추 맞췄던 것 같다. Next.js App Router의 SSR CSR의 특성과 각 컴포넌트 경계에 대해서 한번 더 고민하고 생각해봐야겠다.