Next.js 로딩 UX 완전 정복 – Skeleton, Suspense, 버튼 피드백까지 실전 적용 전략
Next.js 앱에서 로딩 상태 관리와 사용자 경험을 더욱 부드럽게 만드는 팁
Next.js는 빠른 렌더링과 퍼포먼스 중심의 프레임워크지만, 사용자 경험(UX)을 부드럽게 만드는 건 로딩 상태 처리에 달려 있습니다.
화면이 전환될 때, 데이터를 기다리는 동안, 클릭 이후 반응까지. 이 짧은 찰나의 순간이 쌓여 **서비스 전체 인상**을 좌우합니다.
이번 글에서는 Next.js 환경에서 실무적으로 활용할 수 있는 로딩 상태 UX 전략을 전방위로 다뤄봅니다.
- ✅ Spinner vs Skeleton UI
- ✅ Suspense + fallback
- ✅ React Query + isLoading 처리
- ✅ keepPreviousData 전략
- ✅ 페이지 전환 시 로딩 처리
- ✅ 트랜지션과 애니메이션
1. 🌀 기본 로딩 처리 – Spinner는 이제 그만?
✅ 기본 형태: isLoading
const { data, isLoading } = useQuery(['posts'], fetchPosts);
if (isLoading) {
return <Spinner />;
}
하지만 이 방식은 사용자가 보기엔 갑자기 모든 콘텐츠가 사라졌다가 등장하는 UX를 유발할 수 있습니다.
📌 단점
- 레이아웃이 깜빡임
- 사용자 체감상 더 느림
- “기다리는 느낌”이 강하게 남음
2. ✨ Skeleton UI – 사용자가 기다리기 편하게
✅ skeleton은 진짜 콘텐츠의 ‘모양’을 보여줌
import Skeleton from 'react-loading-skeleton';
{isLoading ? (
<Skeleton height={180} />
) : (
<img src={data.image} />
)}
✅ 장점
- 레이아웃 유지 → 깜빡임 방지
- 콘텐츠가 ‘곧 나올 것 같은’ 기대감 형성
- UX 안정감 향상
📦 추천 라이브러리
react-loading-skeleton
@mui/lab/Skeleton
chakra-ui Skeleton
➡️ 로딩 시간 자체는 줄이지 못해도, 기다리는 느낌은 줄일 수 있습니다.
3. ⏳ Suspense + fallback – React 18 이후 필수 전략
React 18부터는 Suspense for Data Fetching이 공식적으로 지원되면서 데이터 기반 UI 로딩 처리가 더 정교해졌습니다.
✅ 기본 구조
import { Suspense } from 'react';
<Suspense fallback={<Skeleton />}>
<PostList />
</Suspense>
✅ 서버 컴포넌트와도 연동 가능 (Next.js 13 이상)
App Router를 사용하는 경우, <suspense />
를 활용해 레이아웃 레벨에서 로딩 UI를 처리할 수 있습니다.
// app/page.tsx
export default function Page() {
return (
<Suspense fallback={<Loading />}>
<Feed />
</Suspense>
);
}
📌 장점
- 컴포넌트 단위 로딩 가능
- SSR과 연계하여 SEO/UX 동시 확보
4. 🔁 페이지 전환 로딩 처리 – 사용자에게 안내하자
📦 next/router events 활용
페이지 이동 시 로딩 UI를 보여주고, 완료되면 제거하는 구조입니다.
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
const usePageLoader = () => {
const router = useRouter();
const [loading, setLoading] = useState(false);
useEffect(() => {
const handleStart = () => setLoading(true);
const handleComplete = () => setLoading(false);
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);
return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleComplete);
};
}, [router]);
return loading;
};
✅ 사용자 피드백 방식
- 페이지 전체 덮는 Spinner
- 상단 프로그레스 바 (예: NProgress)
➡️ 이동이 시작되었고, 완료되지 않았다는 걸 “시각적으로 알려주는 것”이 UX 개선의 핵심입니다.
5. 🧠 keepPreviousData 전략 – 페이지네이션/필터에 필수
✅ React Query의 기능
페이지네이션처럼 queryKey가 변경되더라도 이전 데이터를 유지하면서 부드럽게 전환하도록 도와줍니다.
useQuery({
queryKey: ['posts', page],
queryFn: () => fetchPosts(page),
keepPreviousData: true,
});
📌 UX 장점
- 데이터가 순간적으로 사라지지 않음
- 사용자에게 ‘계속 이어지는’ 느낌 제공
- 페이징 버튼 클릭 시 깜빡임 없음
6. ✨ Optimistic UI – 서버 응답 기다리지 말고 먼저 보여주자
사용자가 어떤 액션을 했을 때 즉각 반응하는 UI는 UX를 획기적으로 향상시킵니다.
📌 낙관적 UI 구성 흐름
- 1. UI 먼저 변경 (setQueryData)
- 2. 서버에 요청 전송
- 3. 성공 시 유지 / 실패 시 롤백
const mutation = useMutation(updateComment, {
onMutate: async (newComment) => {
await queryClient.cancelQueries(['comments']);
const prev = queryClient.getQueryData(['comments']);
queryClient.setQueryData(['comments'], (old) => [...old, newComment]);
return { prev };
},
onError: (err, vars, ctx) => {
queryClient.setQueryData(['comments'], ctx.prev);
},
onSettled: () => {
queryClient.invalidateQueries(['comments']);
}
});
➡️ “버튼 누르고 3초 후에 결과 보이는 것”보다 0.1초 안에 반응하는 UI가 훨씬 좋습니다.
7. 🧷 버튼 클릭 시 로딩 피드백 – 놓치기 쉬운 UX 핵심
✅ 가장 많이 실수하는 부분
- 버튼 클릭 후 반응이 없음
- “잘 눌린 건가?” 하고 사용자 불안
✅ 버튼에 로딩 표시
<button disabled={isSubmitting}>
{isSubmitting ? '처리 중...' : '제출'}
</button>
📌 추가 UX 팁
- 버튼 disable + 스타일 변화
- 스피너 또는 텍스트 전환
- 실패 시 토스트/에러 메시지 제공
➡️ 작은 피드백 하나로 전체 앱의 인상이 바뀝니다.
8. 🧠 마무리 – UX는 디테일에서 시작된다
✅ 총정리 핵심
- Skeleton UI로 깜빡임 없는 로딩 UX 제공
- Suspense fallback으로 SSR + 컴포넌트 단위 제어
- 페이지 이동 시 로딩 애니메이션 or 상단 프로그레스
- keepPreviousData로 데이터 유지
- 낙관적 UI로 빠른 반응 + rollback 처리
- 버튼 클릭 시 로딩 표시와 disable 처리
📌 UX 향상의 3원칙
- 사용자에게 “앱이 반응하고 있다”는 신호를 줄 것
- 콘텐츠가 갑자기 사라지거나 깜빡이지 않게 할 것
- 모든 동작에 대해 “즉각적인 피드백”을 줄 것
Next.js로 성능만 빠른 앱을 만드는 걸 넘어, “사용자가 머물고 싶은 앱”을 만드는 것이 진짜 최적화입니다.
긴 글 읽어주셔서 감사합니다! 공감, 댓글, 공유로 응원해주시면 다음 글 제작에 큰 힘이 됩니다 🙌