From 8aa854381596a486dba48cac17cf5917d316980c Mon Sep 17 00:00:00 2001 From: Hwigeon Date: Sat, 29 Nov 2025 02:30:38 +0900 Subject: [PATCH 1/8] =?UTF-8?q?fix:=20=EB=AF=B8=EB=93=A4=EC=9B=A8=EC=96=B4?= =?UTF-8?q?=EC=97=90=20devtools=20=EB=A1=9C=EA=B7=B8=EA=B0=80=20=EB=9C=A8?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index 6e79b7c..d1bb3b5 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -171,6 +171,6 @@ export const config = { * - _next/image (이미지 최적화) * - favicon.ico (파비콘) */ - '/((?!api|_next/static|_next/image|favicon.ico).*)', + '/((?!api|_next/static|_next/image|favicon.ico|\\.well-known).*)', ], }; From 4a150c8abdcca6b1e32f35f06c97de3963f81892 Mon Sep 17 00:00:00 2001 From: Hwigeon Date: Sat, 29 Nov 2025 03:09:09 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EC=9E=90=EC=9C=A0=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=ED=8C=90=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=ED=94=84=EB=A6=AC=ED=8C=A8=EC=B9=98=ED=95=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/community/freeboard/ClientPage.tsx | 101 ++++++++++++++++++ .../src/app/main/community/freeboard/page.tsx | 97 ++++------------- apps/web/src/components/tabs/PillChipsTab.tsx | 2 + 3 files changed, 124 insertions(+), 76 deletions(-) create mode 100644 apps/web/src/app/main/community/freeboard/ClientPage.tsx diff --git a/apps/web/src/app/main/community/freeboard/ClientPage.tsx b/apps/web/src/app/main/community/freeboard/ClientPage.tsx new file mode 100644 index 0000000..57fb8e7 --- /dev/null +++ b/apps/web/src/app/main/community/freeboard/ClientPage.tsx @@ -0,0 +1,101 @@ +'use client'; + +import React, { useState } from 'react'; +import { useInfiniteQuery } from '@tanstack/react-query'; +import { PillChipsTab } from '@/components/tabs/PillChipsTab'; +import { CATEGORIES, Category } from '../constants/categories'; +import { SortHeader } from '../components/SortHeader'; +import { SortValue } from '@/types/options.types'; +import { SORT_OPTIONS } from '../constants/sortOptions'; +import FloatingButton from '@/components/buttons/FloatingButton'; +import { FreeBoardCard } from '../components/FreeboardCard'; +import CommunityPostList from '../components/CommunityPostList'; +import { FreeboardSummary } from '@/generated/api/models'; +import { + getFreeboardPostsByCursor, + getGetFreeboardPostsByCursorQueryKey, +} from '@/generated/api/endpoints/freeboard/freeboard'; + +/** + * 자유 게시판 클라이언트 메인 페이지 + * + * @description + * - 카테고리별 게시글 목록을 보여주는 페이지 + * - 무한스크롤 기능 포함 + * - 카테고리 및 정렬 옵션 선택 가능 + */ + +export default function FreeboardClientPage() { + const [category, setCategory] = useState(null); + const [sortOption, setSortOption] = useState('LATEST'); + + // 무한스크롤 데이터 페칭 + const { + data, + fetchNextPage, + hasNextPage, + isLoading, + isFetchingNextPage, + error, + refetch, + } = useInfiniteQuery({ + queryKey: getGetFreeboardPostsByCursorQueryKey({ + // queryKey 생성 함수 사용 + category: category ?? undefined, // null일 경우 undefined로 변환 + sort: sortOption, + }), + queryFn: ({ pageParam, signal }) => + getFreeboardPostsByCursor( + { + category: category ?? undefined, + sort: sortOption, + cursor: pageParam, + size: 10, + }, + signal, + ), + initialPageParam: undefined as string | undefined, + getNextPageParam: (lastPage) => { + return lastPage.hasNext ? lastPage.nextCursor : undefined; + }, + }); + + // 모든 페이지의 게시글을 하나의 배열로 합치기 + const allFreeboardPosts: FreeboardSummary[] = + data?.pages.flatMap((page) => page.posts ?? []) ?? []; + // 총 게시글 개수 + const totalCount = data?.pages[0]?.totalCount ?? 0; + + return ( +
+ + chips={CATEGORIES} + activeValue={category} + onChange={setCategory} + showAll + ariaLabel="카테고리 선택 필터" + /> + + + items={allFreeboardPosts} + hasNextPage={hasNextPage || false} + fetchNextPage={fetchNextPage} + isFetchingNextPage={isFetchingNextPage} + initialLoading={isLoading} + error={error} + onRetry={() => refetch()} + getItemKey={(post, index) => post.postId ?? `post-${index}`} + renderItem={(post) => ( + + )} + storageKey="freeboard-post-list-scroll" + /> + +
+ ); +} diff --git a/apps/web/src/app/main/community/freeboard/page.tsx b/apps/web/src/app/main/community/freeboard/page.tsx index 881ad8c..98cdf94 100644 --- a/apps/web/src/app/main/community/freeboard/page.tsx +++ b/apps/web/src/app/main/community/freeboard/page.tsx @@ -1,101 +1,46 @@ -'use client'; -import React, { useState } from 'react'; -import { useInfiniteQuery } from '@tanstack/react-query'; -import { PillChipsTab } from '@/components/tabs/PillChipsTab'; -import { CATEGORIES, Category } from '../constants/categories'; -import { SortHeader } from '../components/SortHeader'; -import { SortValue } from '@/types/options.types'; -import { SORT_OPTIONS } from '../constants/sortOptions'; -import FloatingButton from '@/components/buttons/FloatingButton'; -import { FreeBoardCard } from '../components/FreeboardCard'; -import CommunityPostList from '../components/CommunityPostList'; - -import { FreeboardSummary } from '@/generated/api/models'; +import { + HydrationBoundary, + QueryClient, + dehydrate, +} from '@tanstack/react-query'; import { getFreeboardPostsByCursor, getGetFreeboardPostsByCursorQueryKey, } from '@/generated/api/endpoints/freeboard/freeboard'; +import ClientPage from './ClientPage'; /** - * 자유 게시판 메인 페이지 + * 자유 게시판 메인 페이지 (서버 컴포넌트) * * @description - * - 카테고리별 게시글 목록을 보여주는 페이지 - * - 무한스크롤 기능 포함 - * - 카테고리 및 정렬 옵션 선택 가능 + * - 전체 카테고리 + * - 최신순 정렬 + * - 10개 게시글 프리패치 */ -export default function FreeboardPage() { - const [category, setCategory] = useState(null); - const [sortOption, setSortOption] = useState('LATEST'); +export default async function FreeboardPage() { + const queryClient = new QueryClient(); - // 무한스크롤 데이터 페칭 - const { - data, - fetchNextPage, - hasNextPage, - isLoading, - isFetchingNextPage, - error, - refetch, - } = useInfiniteQuery({ + await queryClient.prefetchInfiniteQuery({ queryKey: getGetFreeboardPostsByCursorQueryKey({ - // queryKey 생성 함수 사용 - category: category ?? undefined, // null일 경우 undefined로 변환 - sort: sortOption, + category: undefined, + sort: 'LATEST', }), - queryFn: ({ pageParam, signal }) => + queryFn: async ({ signal }) => getFreeboardPostsByCursor( - { - category: category ?? undefined, - sort: sortOption, - cursor: pageParam, - size: 10, - }, + { category: undefined, sort: 'LATEST', size: 10 }, signal, ), initialPageParam: undefined as string | undefined, getNextPageParam: (lastPage) => { return lastPage.hasNext ? lastPage.nextCursor : undefined; }, + pages: 1, }); - // 모든 페이지의 게시글을 하나의 배열로 합치기 - const allFreeboardPosts: FreeboardSummary[] = - data?.pages.flatMap((page) => page.posts ?? []) ?? []; - // 총 게시글 개수 - const totalCount = data?.pages[0]?.totalCount ?? 0; - return ( -
- - chips={CATEGORIES} - activeValue={category} - onChange={setCategory} - showAll - ariaLabel="카테고리 선택 필터" - /> - - - items={allFreeboardPosts} - hasNextPage={hasNextPage || false} - fetchNextPage={fetchNextPage} - isFetchingNextPage={isFetchingNextPage} - initialLoading={isLoading} - error={error} - onRetry={() => refetch()} - getItemKey={(post, index) => post.postId ?? `post-${index}`} - renderItem={(post) => ( - - )} - storageKey="freeboard-post-list-scroll" - /> - -
+ + + ); } diff --git a/apps/web/src/components/tabs/PillChipsTab.tsx b/apps/web/src/components/tabs/PillChipsTab.tsx index 74e3467..de09e1b 100644 --- a/apps/web/src/components/tabs/PillChipsTab.tsx +++ b/apps/web/src/components/tabs/PillChipsTab.tsx @@ -1,3 +1,5 @@ +'use client'; + import React, { useState, useRef, useMemo } from 'react'; import { Pressable } from '../Pressable'; import { twMerge } from 'tailwind-merge'; From 835a3bf78bd4dfe1dc72259c1c33fd1710a34e44 Mon Sep 17 00:00:00 2001 From: Hwigeon Date: Sat, 29 Nov 2025 03:17:35 +0900 Subject: [PATCH 3/8] =?UTF-8?q?fix:=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20gap=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/src/app/main/community/components/CommunityPostList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/main/community/components/CommunityPostList.tsx b/apps/web/src/app/main/community/components/CommunityPostList.tsx index 7cda903..5c5ce90 100644 --- a/apps/web/src/app/main/community/components/CommunityPostList.tsx +++ b/apps/web/src/app/main/community/components/CommunityPostList.tsx @@ -104,7 +104,7 @@ export default function CommunityPostList({ {/* 게시글 목록 */} - className={cn('flex flex-col gap-4', className)} + className={cn('flex flex-col', className)} virtualScroll={{ enabled: true, estimateSize: 156, // 카드 평균 높이 From dc5f43336819a3afdb034433e72359621f365d02 Mon Sep 17 00:00:00 2001 From: Hwigeon Date: Sat, 29 Nov 2025 03:40:04 +0900 Subject: [PATCH 4/8] =?UTF-8?q?fix:=20SortHeader=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/app/main/community/components/SortHeader.tsx | 7 +++++-- apps/web/src/app/main/community/freeboard/ClientPage.tsx | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/main/community/components/SortHeader.tsx b/apps/web/src/app/main/community/components/SortHeader.tsx index 7734c3c..ef3652e 100644 --- a/apps/web/src/app/main/community/components/SortHeader.tsx +++ b/apps/web/src/app/main/community/components/SortHeader.tsx @@ -30,7 +30,7 @@ export function SortHeader({ )} aria-label="정렬 옵션" > -

+

총 {totalCount}개 게시글

@@ -39,7 +39,10 @@ export function SortHeader({ onValueChange={(value) => onFilterChange(value as SortValue)} size="sm" > - + {sortOptions.map((option) => ( diff --git a/apps/web/src/app/main/community/freeboard/ClientPage.tsx b/apps/web/src/app/main/community/freeboard/ClientPage.tsx index 57fb8e7..8543997 100644 --- a/apps/web/src/app/main/community/freeboard/ClientPage.tsx +++ b/apps/web/src/app/main/community/freeboard/ClientPage.tsx @@ -80,6 +80,7 @@ export default function FreeboardClientPage() { sortOptions={SORT_OPTIONS} currentValue={sortOption} onFilterChange={setSortOption} + className="px-5" /> items={allFreeboardPosts} From 3fe6c0bcdce00426acfd757bed545d5dd59a7afc Mon Sep 17 00:00:00 2001 From: Hwigeon Date: Sat, 29 Nov 2025 04:15:05 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20formatCount=EA=B0=80=20=ED=95=9C?= =?UTF-8?q?=EA=B8=80=EB=8F=84=20=EC=A7=80=EC=9B=90=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/utils/formatCount.ts | 133 ++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 5 deletions(-) diff --git a/apps/web/src/utils/formatCount.ts b/apps/web/src/utils/formatCount.ts index d1f56c4..abe75cf 100644 --- a/apps/web/src/utils/formatCount.ts +++ b/apps/web/src/utils/formatCount.ts @@ -1,16 +1,139 @@ +/** + * 포맷 타입 + * - 'capped': 99+ 형식 (기본값 99) + * - 'korean': 1.2만, 100만 형식 (한글 약식) + */ +export type FormatType = 'capped' | 'korean'; + /** * 카운트를 cap 기준으로 단순 표기합니다. * - count >= cap → `${cap}+` * - count < cap → 로케일 숫자 + * + * @param count - 표시할 숫자 + * @param options - 옵션 + * @param options.cap - 임계값 (기본: 99) + * @param options.locale - 로케일 (기본: 'ko-KR') + * @param options.fallback - count가 invalid일 때 대체값 (기본: '0') */ export function formatCappedCount( - count: number, + count: number | null | undefined, { - cap = 99, // 99 이상이면 "99+" - locale = 'ko-KR', // 숫자 포맷 로케일 - }: { cap?: number; locale?: string } = {}, + cap = 99, + locale = 'ko-KR', + fallback = '0', + }: { cap?: number; locale?: string; fallback?: string } = {}, ): string { - if (!Number.isFinite(count) || count < 0) return '0'; + if (count == null || !Number.isFinite(count) || count < 0) + return fallback; if (count >= cap) return `${cap}+`; return count.toLocaleString(locale); } + +/** + * 숫자를 한글 약식으로 표기합니다. (1.2천, 1.2만, 100만 등) + * + * @param count - 표시할 숫자 + * @param options - 옵션 + * @param options.precision - 소수점 자릿수 (기본: 1) + * @param options.fallback - count가 invalid일 때 대체값 (기본: '0') + */ +export function formatKoreanCount( + count: number | null | undefined, + { + precision = 1, + fallback = '0', + }: { precision?: number; fallback?: string } = {}, +): string { + if (count == null || !Number.isFinite(count) || count < 0) + return fallback; + + const units = [ + { value: 100_000_000, symbol: '억', nextThreshold: Infinity }, // 억 + { value: 10_000, symbol: '만', nextThreshold: 10000 }, // 만 (10000만 = 1억) + { value: 1_000, symbol: '천', nextThreshold: 10 }, // 천 (10천 = 1만) + ]; + + for (let i = 0; i < units.length; i++) { + const { value, symbol, nextThreshold } = units[i]; + + if (count >= value) { + const divided = count / value; + const formatted = divided.toFixed(precision); + const numericValue = parseFloat(formatted); + + // 반올림 결과가 다음 단위 임계값에 도달하면 다음 단위로 처리 + // 예: 9999 → 10.0천 → 1만, 99999999 → 10000.0만 → 1억 + if (numericValue >= nextThreshold && i > 0) { + const nextUnit = units[i - 1]; + const nextDivided = count / nextUnit.value; + const nextFormatted = nextDivided.toFixed(precision); + const nextCleaned = nextFormatted.replace(/\.0+$/, ''); + const nextParts = nextCleaned.split('.'); + nextParts[0] = parseInt(nextParts[0]).toLocaleString('ko-KR'); + return `${nextParts.join('.')}${nextUnit.symbol}`; + } + + // 소수점 제거 (1.0만 → 1만) + const cleaned = formatted.replace(/\.0+$/, ''); + // 천 단위 콤마 추가 (1234.5만 → 1,234.5만) + const parts = cleaned.split('.'); + parts[0] = parseInt(parts[0]).toLocaleString('ko-KR'); + return `${parts.join('.')}${symbol}`; + } + } + + return count.toLocaleString('ko-KR'); +} + +/** + * 숫자를 다양한 형식으로 표기하는 통합 함수 + * + * @param count - 표시할 숫자 + * @param options - 옵션 + * @param options.type - 포맷 타입 (기본: 'capped') + * @param options.cap - capped 타입일 때 임계값 (기본: 99) + * @param options.precision - korean 타입일 때 소수점 자릿수 (기본: 1) + * @param options.locale - capped 타입일 때 로케일 (기본: 'ko-KR') + * @param options.fallback - count가 invalid일 때 대체값 (기본: '0') + * + * @example + * // Capped 형식 (기본) + * formatCount(50) // '50' + * formatCount(100) // '99+' + * formatCount(100, { cap: 999 }) // '100' + * + * // Korean 형식 + * formatCount(1234, { type: 'korean' }) // '1.2천' + * formatCount(12345, { type: 'korean' }) // '1.2만' + * formatCount(1234567, { type: 'korean' }) // '123.5만' + * formatCount(100000000, { type: 'korean' }) // '1억' + * + * // 대체값 + * formatCount(null, { fallback: '-' }) // '-' + * formatCount(undefined, { fallback: 'N/A' }) // 'N/A' + */ +export function formatCount( + count: number | null | undefined, + { + type = 'capped', + cap = 99, + precision = 1, + locale = 'ko-KR', + fallback = '0', + }: { + type?: FormatType; + cap?: number; + precision?: number; + locale?: string; + fallback?: string; + } = {}, +): string { + switch (type) { + case 'korean': + return formatKoreanCount(count, { precision, fallback }); + case 'capped': + default: + return formatCappedCount(count, { cap, locale, fallback }); + } +} From 7c863970ed3d286f43db3cb99741c106be3a672e Mon Sep 17 00:00:00 2001 From: Hwigeon Date: Sat, 29 Nov 2025 05:16:16 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20=EC=9E=90=EC=9C=A0=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=ED=8C=90=20=EC=B9=B4=EB=93=9C=EC=97=90=20formatCount?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/main/community/components/FreeboardCard.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/main/community/components/FreeboardCard.tsx b/apps/web/src/app/main/community/components/FreeboardCard.tsx index 485b3c2..365eed3 100644 --- a/apps/web/src/app/main/community/components/FreeboardCard.tsx +++ b/apps/web/src/app/main/community/components/FreeboardCard.tsx @@ -5,7 +5,7 @@ import { Category } from '../constants/categories'; import { relativeTime } from '@/utils/relativeTime'; import { Heart, MessageSquareMore } from 'lucide-react'; import { useRouter } from 'next/navigation'; - +import { formatCount } from '@/utils/formatCount'; import type { FreeboardSummary } from '@/generated/api/models'; export interface FreeBoardCardProps { @@ -65,7 +65,7 @@ export function FreeBoardCard({ aria-label={`좋아요 ${likeCount ?? 0}개`} > - {likeCount ?? 0} + {formatCount(likeCount)} {/* 댓글 */}
- {commentCount ?? 0} + + {formatCount(commentCount)} +
From 047fb21a6be292f762d580b18e2d3d102105d225 Mon Sep 17 00:00:00 2001 From: Hwigeon Date: Sat, 29 Nov 2025 05:30:12 +0900 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20=EC=9E=90=EC=9C=A0=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=ED=8C=90=20=EC=B9=B4=EB=93=9C=EC=97=90=20=EC=8D=B8=EB=84=A4?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=A0=9C=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/components/FreeboardCard.tsx | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/apps/web/src/app/main/community/components/FreeboardCard.tsx b/apps/web/src/app/main/community/components/FreeboardCard.tsx index 365eed3..07cd2a3 100644 --- a/apps/web/src/app/main/community/components/FreeboardCard.tsx +++ b/apps/web/src/app/main/community/components/FreeboardCard.tsx @@ -1,4 +1,5 @@ // src/components/CommunityCard.tsx +import Image from 'next/image'; import Card from '@/components/Card'; import { CategoryChip } from '@/components/chips/CategoryChip'; import { Category } from '../constants/categories'; @@ -20,12 +21,12 @@ export function FreeBoardCard({ const { postId, title, - contentPreview, // API에서는 contentPreview 사용 + contentPreview, category, likeCount, commentCount, createdAt, - author, // API에서는 author 사용 + author, } = post; const router = useRouter(); @@ -36,23 +37,35 @@ export function FreeBoardCard({ return ( -
- {isChip && category && ( -
- -
+ {isChip && category && ( +
+ +
+ )} +
+
+

+ {title} +

+

+ {contentPreview} +

+
+ {post.thumbnailUrl && ( + 게시글 썸네일 이미지 )} -

- {title} -

-

- {contentPreview} -

-
+ +
{/* 작성자 · 시간 */} {author?.nickname ?? '알 수 없음'} ·{' '} @@ -78,7 +91,7 @@ export function FreeBoardCard({
- +
); } From 3311fbc6d3c0bb27115b00009733f2d42e1c835d Mon Sep 17 00:00:00 2001 From: Hwigeon Date: Sat, 29 Nov 2025 05:59:33 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20=EC=B9=B4=EB=93=9C=20=ED=8F=89?= =?UTF-8?q?=EA=B7=A0=20=EB=86=92=EC=9D=B4=EB=A5=BC=20=EC=A0=95=ED=99=95?= =?UTF-8?q?=ED=95=98=EA=B2=8C=20=EA=B3=84=EC=82=B0=ED=95=98=EC=97=AC,=20Co?= =?UTF-8?q?mmunityPostList=EC=9D=98=20Gap=20=EB=A6=AC=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=EC=A7=95=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/main/community/components/CommunityPostList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/main/community/components/CommunityPostList.tsx b/apps/web/src/app/main/community/components/CommunityPostList.tsx index 5c5ce90..1c8afbe 100644 --- a/apps/web/src/app/main/community/components/CommunityPostList.tsx +++ b/apps/web/src/app/main/community/components/CommunityPostList.tsx @@ -107,7 +107,7 @@ export default function CommunityPostList({ className={cn('flex flex-col', className)} virtualScroll={{ enabled: true, - estimateSize: 156, // 카드 평균 높이 + estimateSize: 139, // 카드 평균 높이 overscan: 3, }} scrollStore={{ @@ -118,7 +118,7 @@ export default function CommunityPostList({ // 안정적인 key 생성을 위한 함수 필수 전달 getItemKey={getItemKey} renderItem={renderItem} - gap={16} // 4 * 4px = 16px + gap={16} threshold={0.8} > {/* 무한스크롤 트리거 */}