Skip to content

Commit 63a0468

Browse files
authored
[공통] Next.js prefetch 및 ssr 서버 요청으로 인한 페이지 이동 및 로드 속도 저하 문제 수정 (#1227)
1 parent 194d0c2 commit 63a0468

27 files changed

Lines changed: 455 additions & 104 deletions

File tree

next.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { withSentryConfig } from '@sentry/nextjs';
22
/** @type {import('next').NextConfig} */
33
const nextConfig = {
4+
compress: false,
45
webpack(config) {
56
config.module.rules.push({
67
test: /\.svg$/i,

src/api/review/mutations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ interface ReviewMutationCallbacks {
99

1010
const invalidateStoreReviewQueries = async (queryClient: QueryClient, shopId: string) => {
1111
await Promise.all([
12-
queryClient.invalidateQueries({ queryKey: storeQueryKeys.reviews(Number(shopId)) }),
12+
queryClient.invalidateQueries({ queryKey: storeQueryKeys.reviews(Number(shopId), 'public') }),
13+
queryClient.invalidateQueries({ queryKey: storeQueryKeys.reviews(Number(shopId), 'auth') }),
1314
queryClient.invalidateQueries({ queryKey: storeQueryKeys.myReviews(shopId) }),
1415
queryClient.invalidateQueries({ queryKey: storeQueryKeys.detail(shopId) }),
1516
queryClient.invalidateQueries({ queryKey: storeQueryKeys.detailPage(shopId) }),

src/api/store/mutations.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ interface StoreMutationCallbacks {
99

1010
const invalidateStoreReviewQueries = async (queryClient: QueryClient, shopId: string) => {
1111
await Promise.all([
12-
queryClient.invalidateQueries({ queryKey: storeQueryKeys.reviews(Number(shopId)) }),
12+
queryClient.invalidateQueries({ queryKey: storeQueryKeys.reviews(Number(shopId), 'public') }),
13+
queryClient.invalidateQueries({ queryKey: storeQueryKeys.reviews(Number(shopId), 'auth') }),
1314
queryClient.invalidateQueries({ queryKey: storeQueryKeys.myReviews(shopId) }),
1415
queryClient.invalidateQueries({ queryKey: storeQueryKeys.detail(shopId) }),
1516
queryClient.invalidateQueries({ queryKey: storeQueryKeys.detailPage(shopId) }),
@@ -42,7 +43,10 @@ export const storeMutations = {
4243
mutationOptions({
4344
mutationFn: (data: ReviewReportRequest) => postReviewReport(Number(shopId), Number(reviewId), data, token),
4445
onSuccess: async () => {
45-
await queryClient.invalidateQueries({ queryKey: storeQueryKeys.reviews(Number(shopId)) });
46+
await Promise.all([
47+
queryClient.invalidateQueries({ queryKey: storeQueryKeys.reviews(Number(shopId), 'public') }),
48+
queryClient.invalidateQueries({ queryKey: storeQueryKeys.reviews(Number(shopId), 'auth') }),
49+
]);
4650
await callbacks.onSuccess?.();
4751
},
4852
}),

src/api/store/queries.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ interface StoreReviewListQueryParams {
2727
token?: string;
2828
}
2929

30+
export type StoreReviewViewerScope = 'public' | 'auth';
31+
32+
export function getStoreReviewViewerScope(token?: string): StoreReviewViewerScope {
33+
return token ? 'auth' : 'public';
34+
}
35+
3036
export const storeQueryKeys = {
3137
all: ['store'] as const,
3238
categories: () => [...storeQueryKeys.all, 'categories'] as const,
@@ -40,11 +46,12 @@ export const storeQueryKeys = {
4046
benefitCategory: () => [...storeQueryKeys.all, 'benefit-category'] as const,
4147
benefitList: (id: string) => [...storeQueryKeys.all, 'benefit-list', id] as const,
4248
relatedSearch: (query: string) => [...storeQueryKeys.all, 'related-search', query] as const,
43-
reviews: (shopId: number) => ['review', shopId] as const,
44-
reviewFeed: (shopId: number, sorter: string) => [...storeQueryKeys.reviews(shopId), sorter] as const,
45-
reviewList: ({ shopId, page, sorter }: Omit<StoreReviewListQueryParams, 'token'>) =>
46-
[...storeQueryKeys.reviewFeed(shopId, sorter), page] as const,
47-
myReviews: (shopId: string) => ['review', 'my-review', shopId] as const,
49+
reviews: (shopId: number, viewerScope: StoreReviewViewerScope) => ['review', viewerScope, shopId] as const,
50+
reviewFeed: (shopId: number, sorter: string, viewerScope: StoreReviewViewerScope) =>
51+
[...storeQueryKeys.reviews(shopId, viewerScope), sorter] as const,
52+
reviewList: ({ shopId, page, sorter, token }: StoreReviewListQueryParams) =>
53+
[...storeQueryKeys.reviewFeed(shopId, sorter, getStoreReviewViewerScope(token)), page] as const,
54+
myReviews: (shopId: string) => ['review', 'auth', 'my-review', shopId] as const,
4855
myReview: (shopId: string, sorter: string) => [...storeQueryKeys.myReviews(shopId), sorter] as const,
4956
};
5057

@@ -105,13 +112,13 @@ export const storeQueries = {
105112

106113
reviewList: ({ shopId, page, sorter, token }: StoreReviewListQueryParams) =>
107114
queryOptions({
108-
queryKey: storeQueryKeys.reviewList({ shopId, page, sorter }),
115+
queryKey: storeQueryKeys.reviewList({ shopId, page, sorter, token }),
109116
queryFn: () => getReviewList(shopId, page, sorter, token),
110117
}),
111118

112119
reviewFeed: ({ shopId, sorter, token }: Omit<StoreReviewListQueryParams, 'page'>) =>
113120
infiniteQueryOptions({
114-
queryKey: storeQueryKeys.reviewFeed(shopId, sorter),
121+
queryKey: storeQueryKeys.reviewFeed(shopId, sorter, getStoreReviewViewerScope(token)),
115122
initialPageParam: 1,
116123
queryFn: ({ pageParam }) => getReviewList(shopId, pageParam, sorter, token),
117124
getNextPageParam: (lastPage) => {

src/components/IndexComponents/IndexArticles/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ export default function IndexArticles() {
4242
<ul className={styles.list}>
4343
{articlesData?.articles.slice(0, 7).map((article) => (
4444
<li key={article.id} className={styles.list__item}>
45-
<Link href={ROUTES.ArticlesDetail({ id: String(article.id) })} className={styles['list__item-link']}>
45+
<Link
46+
href={ROUTES.ArticlesDetail({ id: String(article.id) })}
47+
prefetch={false}
48+
className={styles['list__item-link']}
49+
>
4650
<span className={styles['list__item-type']}>{convertArticlesTag(article.board_id)}</span>
4751
<span className={styles['list__item-title']}>{article.title}</span>
4852
{article.isNew && (

src/components/Store/StoreDetailPage/components/Review/components/AverageRating/AverageRating.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import styles from './AverageRating.module.scss';
77

88
export default function AverageRating({ id }: { id: string }) {
99
const token = useTokenState();
10-
const { data } = useSuspenseInfiniteQuery(storeQueries.reviewFeed({ shopId: Number(id), sorter: 'LATEST', token }));
10+
11+
const { data } = useSuspenseInfiniteQuery({
12+
...storeQueries.reviewFeed({ shopId: Number(id), sorter: 'LATEST', token }),
13+
});
1114
const totalReviewCount = data.pages[0].total_count;
1215

1316
const ratingObject = data.pages[0].statistics;

src/components/Store/StoreDetailPage/components/Review/components/ReviewList/ReviewList.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,14 @@ export default function ReviewList({ id }: { id: string }) {
4040
const previousSortType = useDeferredValue(currentSortType);
4141
const currentSortLabel = typeToLabel[currentSortType];
4242
const token = useTokenState();
43-
const { data, hasNextPage, fetchNextPage } = useSuspenseInfiniteQuery(
44-
storeQueries.reviewFeed({
43+
44+
const { data, hasNextPage, fetchNextPage } = useSuspenseInfiniteQuery({
45+
...storeQueries.reviewFeed({
4546
shopId: Number(id),
4647
sorter: previousSortType,
4748
token,
4849
}),
49-
);
50+
});
5051
const reviews = data.pages.flatMap((page) => page.reviews);
5152
const { data: myReview } = useQuery({
5253
...storeQueries.myReview(id, previousSortType, token),

src/components/layout/Footer/index.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ function Footer(): JSX.Element {
7676
<li className={styles.footer__service} key={submenuInfo.title}>
7777
<Link
7878
href={isStage && submenuInfo.stageLink ? submenuInfo.stageLink : submenuInfo.link}
79+
prefetch={false}
7980
onClick={(e) => handleClickMenu(e, submenuInfo.title)}
8081
>
8182
{submenuInfo.title}
@@ -117,9 +118,11 @@ function Footer(): JSX.Element {
117118
아우누리 바로가기
118119
</a>
119120
</li>
120-
<Link href={ROUTES.PrivatePolicy()} className={styles.sitemap__link}>
121-
개인정보 처리방침
122-
</Link>
121+
<li className={styles.sitemap__link}>
122+
<Link href={ROUTES.PrivatePolicy()} prefetch={false}>
123+
개인정보 처리방침
124+
</Link>
125+
</li>
123126
</ul>
124127
) : (
125128
<ul className={styles.sitemap__content}>
@@ -143,9 +146,11 @@ function Footer(): JSX.Element {
143146
BCSD Lab 바로가기
144147
</a>
145148
</li>
146-
<Link href={ROUTES.PrivatePolicy()} className={styles.sitemap__link}>
147-
개인정보 처리방침
148-
</Link>
149+
<li className={styles.sitemap__link}>
150+
<Link href={ROUTES.PrivatePolicy()} prefetch={false}>
151+
개인정보 처리방침
152+
</Link>
153+
</li>
149154
</ul>
150155
)}
151156
<div className={styles['sitemap__icon-links']}>

src/components/layout/Header/PCHeader/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,12 @@ export default function PCHeader({ openModal }: PCHeaderProps) {
242242
return (
243243
<li className={styles.megamenu__menu} key={menu.title}>
244244
{/* TODO: 키보드 Focus 접근성 향상 */}
245-
<Link className={styles.megamenu__link} href={href} onClick={(e) => handleMenuClick(e, menu.title)}>
245+
<Link
246+
className={styles.megamenu__link}
247+
href={href}
248+
prefetch={false}
249+
onClick={(e) => handleMenuClick(e, menu.title)}
250+
>
246251
{menu.title}
247252
</Link>
248253
</li>
@@ -257,6 +262,7 @@ export default function PCHeader({ openModal }: PCHeaderProps) {
257262
<li className={styles['header__auth-link']}>
258263
<Link
259264
href={ROUTES.AuthSignup({ currentStep: '약관동의' })}
265+
prefetch={false}
260266
onClick={() => {
261267
sessionLogger.actionSessionEvent({
262268
session_name: 'sign_up',
@@ -272,6 +278,7 @@ export default function PCHeader({ openModal }: PCHeaderProps) {
272278
<li className={styles['header__auth-link']}>
273279
<Link
274280
href={ROUTES.Auth()}
281+
prefetch={false}
275282
onClick={() => {
276283
logger.actionEventClick({
277284
team: 'USER',

src/components/ui/Banner/index.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useSwipeable } from 'react-swipeable';
77
import useLogger from 'utils/hooks/analytics/useLogger';
88
import useMediaQuery from 'utils/hooks/layout/useMediaQuery';
99
import useBooleanState from 'utils/hooks/state/useBooleanState';
10-
import { setCookie } from 'utils/ts/cookie';
10+
import { getCookie, setCookie } from 'utils/ts/cookie';
1111
import styles from './Banner.module.scss';
1212

1313
interface BannerCardProps {
@@ -63,18 +63,34 @@ function BannerCard({ handleImageLinkClick, image_url, redirect_link, isFirst }:
6363
interface BannerProps {
6464
bannersList: BannersResponse;
6565
bannerCategoryId: number;
66-
isBannerOpen: boolean;
6766
}
6867

6968
const HIDE_BANNER_DURATION_DAYS = 7;
69+
const HIDE_BANNER_COOKIE = 'HIDE_BANNER';
7070

71-
function Banner({ bannersList, bannerCategoryId, isBannerOpen }: BannerProps) {
71+
function Banner({ bannersList, bannerCategoryId }: BannerProps) {
7272
const isMobile = useMediaQuery();
7373
const intervalRef = useRef<NodeJS.Timeout | null>(null);
7474
const logger = useLogger();
7575
const [currentPageIndex, setCurrentPageIndex] = useState(0);
7676
const currentBanner = bannersList.banners[currentPageIndex];
77-
const [isModalOpen, , closeModal] = useBooleanState(isBannerOpen);
77+
const [isModalOpen, openModal, closeModal] = useBooleanState(false);
78+
79+
useEffect(() => {
80+
if (bannersList.count === 0) {
81+
closeModal();
82+
return;
83+
}
84+
85+
// 배너 노출 여부를 SSR에서 분리해 페이지 HTML 캐시를 안전하게 유지
86+
const isBannerHidden = getCookie(HIDE_BANNER_COOKIE) === `modal_category_${bannerCategoryId}`;
87+
if (isBannerHidden) {
88+
closeModal();
89+
return;
90+
}
91+
92+
openModal();
93+
}, [bannerCategoryId, bannersList.count, closeModal, openModal]);
7894

7995
const resetAutoSlide = useCallback(() => {
8096
if (intervalRef.current) clearTimeout(intervalRef.current);
@@ -128,7 +144,7 @@ function Banner({ bannersList, bannerCategoryId, isBannerOpen }: BannerProps) {
128144
value: `${currentBanner.title}`,
129145
});
130146
if (bannerCategoryId === 1) {
131-
setCookie('HIDE_BANNER', `modal_category_${bannerCategoryId}`, HIDE_BANNER_DURATION_DAYS);
147+
setCookie(HIDE_BANNER_COOKIE, `modal_category_${bannerCategoryId}`, HIDE_BANNER_DURATION_DAYS);
132148
}
133149
closeModal();
134150
};

0 commit comments

Comments
 (0)