Skip to content

Comments

[FEAT] og 태그 설정 변경#148

Merged
sunhwaaRj merged 4 commits intodevelopfrom
feature/#146-og
Jan 27, 2026
Merged

[FEAT] og 태그 설정 변경#148
sunhwaaRj merged 4 commits intodevelopfrom
feature/#146-og

Conversation

@sunhwaaRj
Copy link
Contributor

@sunhwaaRj sunhwaaRj commented Jan 27, 2026

✅ PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📌 관련 이슈번호


✅ Key Changes

  • 기본 페이지 og태그 추가
  • 메타데이터 이미지 경로 변경

📸 스크린샷 or 실행영상


🎸 기타 사항 or 추가 코멘트

Summary by CodeRabbit

  • New Features

    • 페이지별 메타데이터 생성 추가로 각 페이지의 제목, 설명 및 소셜 미리보기(OG) 정보 개선
    • 프로젝트 목록에 새 반응형 컴포넌트 도입: 필터, 데스크탑 페이지네이션, 모바일 무한 스크롤, 로딩/오류 처리 및 간결한 페이지 렌더링
  • Bug Fixes

    • Open Graph 이미지 URL을 절대 HTTPS로 변경해 외부 공유 시 이미지 일관성 개선
    • 일부 메타 제목 문구 소폭 조정

✏️ Tip: You can customize this high-level summary in your review settings.

@sunhwaaRj sunhwaaRj self-assigned this Jan 27, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 27, 2026

Walkthrough

페이지별 메타데이터 생성 함수가 추가/갱신되고, 여러 경로의 Open Graph 이미지 URL이 절대 HTTPS로 변경되었습니다. 또한 프로젝트 목록 UI가 기존 복잡한 클라이언트 페이지에서 새로운 ProjectListPage 클라이언트 컴포넌트로 대체되었습니다. (50단어 이내)

Changes

Cohort / File(s) 요약
메타데이터 생성 함수 추가
src/app/(main)/page.tsx, src/app/(main)/project/page.tsx
Metadata 타입을 import 하고 export async function generateMetadata(): Promise<Metadata> 추가 (title, description, openGraph 포함)
OG 이미지 URL 변경
src/app/(main)/question/[uuid]/page.tsx, src/app/(main)/teampsylog/head/[uuid]/page.tsx
Open Graph 이미지 경로를 상대 경로에서 절대 HTTPS URL로 교체 (이미지 URL 변경만)
프로젝트 목록 컴포넌트 도입
src/app/(main)/project/page.tsx, src/app/(main)/project/_components/ProjectList.tsx
기존 클라이언트 페이지 로직을 제거하고 ProjectListPage 클라이언트 컴포넌트 추가·사용: 필터, 페이징, 모바일 무한 스크롤, 반응형 레이아웃, 데이터 페칭 훅 통합 등

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Client as ProjectListPage
  participant API
  participant Router

  rect rgba(0,128,255,0.5)
  User->>Client: 페이지 접근
  Client->>API: 초기 데이터 요청 (useRecruitingPosts)
  API-->>Client: 초기 데이터 응답
  Client->>Client: 데스크탑/모바일 렌더링 분기
  end

  rect rgba(0,200,100,0.5)
  Note over Client,User: 모바일 스크롤에 따른 추가 로드
  User->>Client: 스크롤 → 관찰 영역 진입
  Client->>Client: intersection observer 트리거
  Client->>API: 추가 페이지 요청 (useInfiniteRecruitingPosts)
  API-->>Client: 추가 데이터 응답
  Client->>Client: displayCount 업데이트 및 렌더링
  end

  Router-->>Client: 카드 클릭 → 상세 페이지 네비게이션
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

FEATURE

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning ProjectList.tsx 새 컴포넌트 추가는 이슈 #146의 OG 태그 설정과 직접적인 관련이 없어 범위를 벗어난 변경으로 보입니다. ProjectList.tsx 추가 변경의 범위를 명확히 하거나, 별도의 이슈로 분리하여 관리하는 것을 권장합니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 OG 태그 설정 변경이라는 주요 변경사항을 명확하게 전달하며, 변경사항의 핵심을 적절히 요약하고 있습니다.
Description check ✅ Passed PR 설명은 템플릿을 따르고 있으며 PR 유형(새로운 기능 추가, 버그 수정)과 핵심 변경사항(기본 페이지 og태그 추가, 메타데이터 이미지 경로 변경)을 포함하고 있습니다.
Linked Issues check ✅ Passed PR은 이슈 #146의 OG 태그 설정 요구사항을 충족합니다. 여러 페이지에 generateMetadata 함수를 추가하고 이미지 경로를 절대 HTTPS URL로 변경했습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/app/`(main)/project/page.tsx:
- Around line 20-38: generateMetadata is a server-only export but your page
currently exposes client-side code ('use client'); split the page into a server
page that exports generateMetadata and renders a separate client component for
interactive logic. Move client logic into a new component (e.g.,
ProjectPageContent) without exporting generateMetadata from it, keep
generateMetadata in page.tsx (server component) and import/render
ProjectPageContent there, and remove any 'use client' directive from the file
that defines generateMetadata so the server API is used correctly.
🧹 Nitpick comments (1)
src/app/(main)/teampsylog/head/[uuid]/page.tsx (1)

12-46: 중복 코드 리팩토링을 고려해보세요.

generateMetadata 함수가 question/[uuid]/page.tsx와 거의 동일합니다. 공통 유틸리티 함수로 추출하면 유지보수성이 향상됩니다.

♻️ 예시: 공통 메타데이터 생성 함수
// lib/metadata.ts
export async function generateUserMetadata(uuid: string): Promise<Metadata> {
  let userName = '사용자';
  try {
    const res = await axios.get(
      `${process.env.NEXT_PUBLIC_API_BASE_URL}/teamficial-log/requester`,
      { params: { requesterUuid: uuid } },
    );
    userName = res.data?.result?.requesterName ?? '사용자';
  } catch (error) {
    console.error('Failed to fetch requester data:', error);
  }
  return {
    title: `${userName}님의 팀피셜록을 작성해볼까요?`,
    description: `소프트스킬 팀빌딩 서비스, 팀피셜`,
    openGraph: {
      title: `${userName}님의 팀피셜록을 작성해볼까요?`,
      description: `소프트스킬 팀빌딩 서비스, 팀피셜`,
      images: [{ url: 'https://www.teamficial.com/og/Teamficial_metatag_Image.jpg', width: 1200, height: 630 }],
    },
  };
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/app/`(main)/project/_components/ProjectList.tsx:
- Around line 35-94: The desktop and mobile query states are being merged
causing global Loading/Error UI; update the hooks useRecruitingPosts and
useInfiniteRecruitingPosts calls to run only when their viewport is active by
adding React Query's enabled flag (e.g., enabled: isDesktopView for
useRecruitingPosts and enabled: isMobileView for useInfiniteRecruitingPosts) so
inactive queries do not fetch or contribute to isLoading/isError; then adjust
isInitialLoading and error checks to consider only the active view's flags (use
isLoading/isError for desktop when isDesktopView, and
isMobileInitialLoading/isMobileError for mobile when isMobileView). Ensure you
reference the existing variables currentPage/pageSize, mobilePageSize, and
displayCount logic unchanged.
🧹 Nitpick comments (1)
src/app/(main)/project/_components/ProjectList.tsx (1)

61-83: inView 연속 상태에서 복합적인 페이지 요청 재트리거 시나리오 우려

현재 코드는 !isFetchingNextPage 가드로 동시 요청을 이미 방지하고 있으나, 다음 흐름에서 재트리거 가능성이 있습니다:

  • fetchNextPage() 완료 → allMobileCards.length 증가 → 첫 번째 useEffect 재실행
  • nextDisplayCount > allMobileCards.length 조건이 여전히 참이면 추가 요청 발생
  • 두 번째 useEffect에서 isFetchingNextPagedisplayCount 클램프를 조건부로 처리하여, 페칭 중에는 첫 번째 effect의 의존성 변경을 최소화하면 흐름이 더 명확합니다.

또한 rootMargin을 활용하여 스크롤 끝에서의 빠른 진입/이탈 토글을 줄이는 것도 도움이 됩니다:

const { ref, inView } = useInView({ rootMargin: '200px 0px', threshold: 0 })

Comment on lines +35 to +94
const { data, isLoading, isError } = useRecruitingPosts(filters, currentPage, pageSize);

const {
data: infiniteData,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading: isMobileLoading,
isError: isMobileError,
isInitialLoading: isMobileInitialLoading,
} = useInfiniteRecruitingPosts(filters, mobilePageSize);

const [displayCount, setDisplayCount] = useState(initialDisplayCount);

useEffect(() => {
setDisplayCount(initialDisplayCount);
}, [filters]);

const { ref, inView } = useInView({
threshold: 0,
});

const allMobileCards = infiniteData?.pages.flatMap((page) => page.content) ?? [];

const mobileCards = allMobileCards.slice(0, displayCount);

useEffect(() => {
if (inView && hasNextPage && !isFetchingNextPage) {
const nextDisplayCount = displayCount + loadMoreCount;

if (nextDisplayCount > allMobileCards.length) {
fetchNextPage();
} else {
setDisplayCount(nextDisplayCount);
}
}
}, [inView, hasNextPage, isFetchingNextPage, displayCount, allMobileCards.length, fetchNextPage]);

useEffect(() => {
if (infiniteData && allMobileCards.length > 0) {
const currentTotalCards = allMobileCards.length;
if (!hasNextPage && displayCount < currentTotalCards) {
setDisplayCount(currentTotalCards);
}
if (displayCount > currentTotalCards) {
setDisplayCount(Math.max(initialDisplayCount, currentTotalCards));
}
}
}, [infiniteData, hasNextPage, displayCount, allMobileCards.length]);

const handleFilterChange = (newFilters: Filters) => {
setFilters(newFilters);
setCurrentPage(1);
};

const isInitialLoading = (isLoading && currentPage === 1) || isMobileInitialLoading;

if (isInitialLoading) return <Loading />;
if (isError || isMobileError) return <ErrorDisplay />;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

@tanstack/react-query v5 enabled option conditional queries best practices

💡 Result:

Best practices for conditional (“dependent/lazy”) queries in @tanstack/react-query v5

  1. Gate the query with enabled when a required input isn’t ready

    • Use enabled: !!param (or a boolean function) so the query doesn’t run until the dependency exists (e.g., an id). This is the recommended pattern for dependent/serial queries. [2]
  2. Prefer skipToken for TypeScript-safe conditional query functions

    • If your query should be disabled when a value is missing, you can keep strong types by returning skipToken instead of a queryFn. [1]
  3. If you need manual triggering (refetch()), prefer enabled: false over skipToken

    • refetch() does not work with skipToken (it will error because there’s no queryFn). If you want a “lazy query” button, use enabled: false and call refetch(). [1]
  4. Use the right loading flag for disabled/lazy queries

    • Disabled queries can be status: 'pending' while not fetching (fetchStatus: 'idle'), so isPending can be misleading. Use isLoading (derived from isPending && isFetching) to show “first load” spinners. [1]
  5. Avoid request waterfalls when possible

    • Dependent queries are inherently a waterfall (slower than parallel). If you can, restructure the API to fetch what you need in one request (or otherwise in parallel). [2]

Common v5 patterns

Dependent query (enabled) [2]

const { data: user } = useQuery({ queryKey: ['user', email], queryFn: getUserByEmail })
const userId = user?.id

const projectsQuery = useQuery({
  queryKey: ['projects', userId],
  queryFn: getProjectsByUser,
  enabled: !!userId,
})

TS-safe conditional with skipToken (no manual refetch) [1]

const todosQuery = useQuery({
  queryKey: ['todos', filter],
  queryFn: filter ? () => fetchTodos(filter) : skipToken,
})

Dynamic useQueries: return [] until inputs exist [2]

const usersMessages = useQueries({
  queries: userIds
    ? userIds.map(id => ({ queryKey: ['messages', id], queryFn: () => getMessagesByUsers(id) }))
    : [],
})

Sources: TanStack Query v5 docs on disabling/pausing queries and skipToken [1], and dependent queries + performance note [2].


🌐 Web query:

React Query v5 useQuery useInfiniteQuery enabled option documentation

💡 Result:

  • useQuery docs (v5): The enabled option is documented as boolean | (query: Query) => boolean. Setting it to false disables the query from automatically running (useful for dependent/lazy queries). [1]
  • Disabling/Pausing Queries guide (v5): Explains behavior when enabled: false (no automatic fetch/refetch; can still refetch() manually; ignores invalidateQueries/refetchQueries triggers; initial state differs depending on cached data). [2]
  • useInfiniteQuery docs: useInfiniteQuery options are “identical to useQuery” (so it supports enabled) plus infinite-specific options like initialPageParam and getNextPageParam. [3]

Sources:
[1] TanStack Query v5 useQuery reference (enabled option) (tanstack.com)
[2] TanStack Query v5 Disabling/Pausing Queries guide (tanstack.dev)
[3] TanStack Query useInfiniteQuery reference (options identical to useQuery) (tanstack.com)

Citations:


🌐 Web query:

viewport-based conditional query execution React Query best practices

💡 Result:

Best practices for viewport-based conditional query execution (TanStack/React Query)

  1. Never call useQuery conditionally—control execution with enabled

    • Keep the hook mounted, and gate network execution with enabled: inView (or a boolean function). This is the intended “pause/disable” mechanism. [1]
  2. Combine viewport + dependency readiness in enabled

    • For queries that also depend on props/state (e.g., id), gate on both:
      • enabled: inView && !!id
    • This matches the “dependent query” pattern (don’t execute until the dependency exists). [2]
  3. Type-safe conditional execution (TS): use skipToken when you want to omit the queryFn

    • If your query function is only valid when in-view (or when params exist), you can return skipToken to disable the query in a type-safe way. Note: refetch() won’t work with skipToken, so use enabled: false if you need manual refetch. [3]
  4. Prefer prefetching before in-view for perceived performance

    • If you can predict intent (e.g., the component is near the viewport), prefetch into cache with queryClient.prefetchQuery (or related helpers). Then, when it becomes visible, useQuery can render instantly from cache. [4]
  5. For list/pagination/infinite patterns, avoid UI “jumps” while loading

    • When the query key changes (page/cursor), use placeholderData (or the keepPreviousData helper in v5) to keep the previous page’s data visible while fetching the next page. [5]

Minimal pattern (viewport → enabled)

const { ref, inView } = useInView({ rootMargin: '200px' }) // “near viewport”

const query = useQuery({
  queryKey: ['item', id],
  queryFn: () => fetchItem(id),
  enabled: inView && !!id,
})

Sources: Disabling/pausing queries via enabled [1], dependent queries via enabled [2], skipToken behavior and refetch limitation [3], prefetching guidance [4], pagination with placeholderData / keepPreviousData helper [5].

[1] (tanstack.dev)
[2] (tanstack.com)
[3] (tanstack.com)
[4] (tanstack.com)
[5] (tanstack.com)

Citations:


데스크톱/모바일 쿼리 상태를 합산해 로딩·에러가 잘못 노출됨

현재 isInitialLoading/isError가 데스크톱(useRecruitingPosts)과 모바일(useInfiniteRecruitingPosts) 상태를 합산합니다. 모바일 무한 스크롤이 실패하거나 로딩 중이면 데스크톱 화면도 전체가 Loading/Error로 막힙니다.

React Query v5의 enabled 옵션으로 뷰포트 기반 활성 쿼리만 실행하도록 분리하세요. 이는 비활성 쿼리가 불필요하게 fetch되는 것을 방지하고, 상태 병합 문제를 근본적으로 해결합니다.

✅ 제안 수정
+  const [isTabletUp, setIsTabletUp] = useState(false);
+  useEffect(() => {
+    const mq = window.matchMedia('(min-width: 768px)');
+    const sync = () => setIsTabletUp(mq.matches);
+    sync();
+    mq.addEventListener('change', sync);
+    return () => mq.removeEventListener('change', sync);
+  }, []);
+
-  const { data, isLoading, isError } = useRecruitingPosts(filters, currentPage, pageSize);
+  const { data, isLoading, isError } = useRecruitingPosts(
+    filters,
+    currentPage,
+    pageSize,
+    { enabled: isTabletUp }
+  );

-  const {
-    data: infiniteData,
-    fetchNextPage,
-    hasNextPage,
-    isFetchingNextPage,
-    isLoading: isMobileLoading,
-    isError: isMobileError,
-    isInitialLoading: isMobileInitialLoading,
-  } = useInfiniteRecruitingPosts(filters, mobilePageSize);
+  const {
+    data: infiniteData,
+    fetchNextPage,
+    hasNextPage,
+    isFetchingNextPage,
+    isLoading: isMobileLoading,
+    isError: isMobileError,
+    isInitialLoading: isMobileInitialLoading,
+  } = useInfiniteRecruitingPosts(filters, mobilePageSize, { enabled: !isTabletUp });

-  const isInitialLoading = (isLoading && currentPage === 1) || isMobileInitialLoading;
+  const isInitialLoading = isTabletUp
+    ? isLoading && currentPage === 1
+    : isMobileInitialLoading;
   if (isInitialLoading) return <Loading />;
-  if (isError || isMobileError) return <ErrorDisplay />;
+  if (isTabletUp ? isError : isMobileError) return <ErrorDisplay />;
🤖 Prompt for AI Agents
In `@src/app/`(main)/project/_components/ProjectList.tsx around lines 35 - 94, The
desktop and mobile query states are being merged causing global Loading/Error
UI; update the hooks useRecruitingPosts and useInfiniteRecruitingPosts calls to
run only when their viewport is active by adding React Query's enabled flag
(e.g., enabled: isDesktopView for useRecruitingPosts and enabled: isMobileView
for useInfiniteRecruitingPosts) so inactive queries do not fetch or contribute
to isLoading/isError; then adjust isInitialLoading and error checks to consider
only the active view's flags (use isLoading/isError for desktop when
isDesktopView, and isMobileInitialLoading/isMobileError for mobile when
isMobileView). Ensure you reference the existing variables currentPage/pageSize,
mobilePageSize, and displayCount logic unchanged.

@sunhwaaRj sunhwaaRj merged commit b2d60db into develop Jan 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] OG 태그 수정

1 participant