Skip to content

Comments

피드 UX 개선 게시글 이동 방식 변경 및 스크롤 위치 복원#312

Open
ho0010 wants to merge 6 commits intorefactorfrom
fix/feed-ux
Open

피드 UX 개선 게시글 이동 방식 변경 및 스크롤 위치 복원#312
ho0010 wants to merge 6 commits intorefactorfrom
fix/feed-ux

Conversation

@ho0010
Copy link
Collaborator

@ho0010 ho0010 commented Feb 25, 2026

📝작업 내용

  • 피드에서 게시물을 클릭하면 새 탭(window.open)으로 열려 UX가 좋지 않았고, 같은 탭으로 이동하도록 변경하면 뒤로 돌아올 때 스크롤 위치가 초기화되는 문제가 새로 발생함.

상세 변경 사항

  1. 새 탭 → 같은 화면 navigate 전환
  • PostBody, PostFooter의 window.open('/feed/:id', '_blank') → navigate('/feed/:id')
  • FeedDetailPage 뒤로가기 핸들러의 window.close() / window.opener 체크 제거 → navigate(-1)
  1. 피드 목록 스크롤 위치 보존
  • 피드 데이터(게시물 목록, 커서, 탭 상태)를 sessionStorage에 캐싱
  • 상세 페이지에서 돌아올 때 API 재호출 없이 캐시 데이터로 즉시 렌더링 후 저장된 scrollY로 복원
  • 캐시 TTL: 10분 / 스크롤 저장: 100ms 디바운스
  1. 캐시 로직 훅으로 분리 (useFeedCache)
  • Feed.tsx에 있던 캐시 관련 상수/인터페이스/함수를
  • useFeedCache.ts로 이전
    스크롤 복원(rAF), 스크롤 디바운스 저장을 훅 내부에 캡슐화
  • writeFeedCache() export로 Feed에서 명시적으로 호출
  1. 피드 상세 진입 시 흰 화면 깜빡임 개선
  • 로딩 중 return <></> → 로 교체해 어두운 배경 유지
    error / !feedData 상태의 3개 분기도 하나로 통합
  1. 캐시 저장 조건 버그 수정
  • totalFeedPosts.length === 0 가드로 인해 '내 피드' 탭 진입 시 캐시가 영구 미저장되던 문제 수정
    현재 활성 탭 기준으로 조건 변경: activeTab === '피드' ? totalFeedPosts.length > 0 : myFeedPosts.length > 0

개선전

2026-02-25.5.38.58.mov

개선후

2026-02-25.5.45.12.mov

- 새 탭(window.open) 대신 같은 화면에서 navigate()로 이동하도록 변경
  - PostBody: 게시물 본문 클릭 시 window.open → navigate('/feed/:feedId')
  - PostFooter: 댓글 아이콘 클릭 시 window.open → navigate('/feed/:feedId')

- FeedDetailPage: 뒤로가기 핸들러 정리
  - window.close() / window.opener 체크 제거
  - navigate(-1) 단일 호출로 단순화

- Feed: 피드 목록 상태 및 스크롤 위치를 sessionStorage에 캐싱하여 복원
  - 게시물 상세에서 돌아올 때 API 재호출 없이 캐시 데이터로 즉시 렌더링
  - 10분 TTL 초과 시 자동으로 캐시 무효화
  - 스크롤 이벤트(100ms 디바운스)로 scrollY를 sessionStorage에 주기 저장
  - 마운트 시 저장된 scrollY로 스크롤 위치 복원(rAF 2회)
  - 탭 전환 시 scrollTo(0, 0) 정상 동작 유지
  - navigate(state: { initialTab }) 진입 시 캐시 무시하고 새로 로드
@vercel
Copy link

vercel bot commented Feb 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
thip Ready Ready Preview, Comment Feb 25, 2026 8:46am

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Walkthrough

클라이언트 사이드 라우팅을 도입하여 Post 컴포넌트에서 window.open 기반 네비게이션을 useNavigate로 변경하고, Feed 페이지에 sessionStorage 기반의 상태 캐싱 메커니즘을 추가했으며, FeedDetailPage의 뒤로 가기 로직을 단순화했습니다.

Changes

Cohort / File(s) Summary
Post 컴포넌트 네비게이션
src/components/common/Post/PostBody.tsx, src/components/common/Post/PostFooter.tsx
window.open 호출을 useNavigate 훅으로 대체하여 새 탭 열기에서 인앱 네비게이션으로 변경
Feed 페이지 캐싱
src/pages/feed/Feed.tsx
sessionStorage 기반 캐싱 시스템 추가. activeTab, 포스트 목록, 커서, 스크롤 위치 등을 TTL과 함께 캐시하여 탭 전환 시 상태 복원
Feed 상세 페이지 네비게이션
src/pages/feed/FeedDetailPage.tsx
window 기반 뒤로 가기 로직을 단순한 navigate(-1) 호출로 통합

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

✨ Feature

Poem

🐰 윈도우 창 닫고 라우터를 열어,
부드러운 인앱 네비게이션으로,
캐시된 상태 복원하며 스크롤도 기억하고,
Feed는 더 빠르게, 더 자연스럽게 흐르네!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed PR 제목이 변경사항의 핵심을 명확하게 요약하고 있습니다. 게시글 이동 방식 변경(window.open → navigate)과 스크롤 위치 복원이라는 두 가지 주요 개선사항을 간결하게 표현하고 있습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/feed-ux

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: 2

🧹 Nitpick comments (3)
src/pages/feed/Feed.tsx (3)

33-43: 만료된 캐시가 sessionStorage에서 제거되지 않습니다.

getInitialFeedCache에서 TTL이 만료된 경우 null을 반환하지만, 만료된 항목을 sessionStorage에서 삭제하지 않습니다. 큰 문제는 아니지만, 정리하면 storage 공간을 확보할 수 있습니다.

♻️ 만료 시 캐시 제거 제안
 function getInitialFeedCache(): FeedCache | null {
   try {
     const raw = sessionStorage.getItem(FEED_CACHE_KEY);
     if (!raw) return null;
     const cache = JSON.parse(raw) as FeedCache;
-    if (Date.now() - cache.timestamp < FEED_CACHE_TTL) return cache;
+    if (Date.now() - cache.timestamp < FEED_CACHE_TTL) return cache;
+    sessionStorage.removeItem(FEED_CACHE_KEY);
   } catch {
     // ignore
   }
   return null;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/feed/Feed.tsx` around lines 33 - 43, getInitialFeedCache currently
returns null for expired caches but leaves the stale entry in sessionStorage;
update the function (getInitialFeedCache) to remove the expired item by calling
sessionStorage.removeItem(FEED_CACHE_KEY) when the TTL check fails (i.e., when
Date.now() - cache.timestamp >= FEED_CACHE_TTL) so the stale data is cleaned up
before returning null; keep the existing try/catch behavior for parse errors.

148-158: loadMoreFeeds의 의존성 배열에 loadTotalFeedsloadMyFeeds가 누락되어 있습니다.

두 함수는 현재 빈 의존성 배열로 안정적이지만, 향후 변경 시 stale closure 문제가 발생할 수 있고 react-hooks/exhaustive-deps 경고가 발생할 수 있습니다.

♻️ 의존성 배열 보완
-  }, [activeTab, totalIsLast, totalLoading, totalNextCursor, myIsLast, myLoading, myNextCursor]);
+  }, [activeTab, totalIsLast, totalLoading, totalNextCursor, myIsLast, myLoading, myNextCursor, loadTotalFeeds, loadMyFeeds]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/feed/Feed.tsx` around lines 148 - 158, The useCallback for
loadMoreFeeds is missing loadTotalFeeds and loadMyFeeds in its dependency array
which can cause stale closures and lint warnings; update the dependency array of
the useCallback that defines loadMoreFeeds to include loadTotalFeeds and
loadMyFeeds, or if those functions are unstable, memoize them (e.g., wrap
loadTotalFeeds/loadMyFeeds with useCallback where they are declared) so
loadMoreFeeds remains correct and lint-clean.

239-239: sessionStorage.setItem에서 QuotaExceededError가 발생할 수 있습니다.

피드 목록이 많거나 contentUrls에 긴 URL이 포함된 경우 직렬화된 데이터가 sessionStorage 용량(~5MB)을 초과할 수 있습니다. setItem 호출을 try-catch로 감싸거나, 캐시할 포스트 수에 상한을 두는 것을 고려해 주세요. Line 212의 scroll persistence 쪽도 동일합니다.

🛡️ setItem에 try-catch 추가 제안
-    sessionStorage.setItem(FEED_CACHE_KEY, JSON.stringify(cache));
+    try {
+      sessionStorage.setItem(FEED_CACHE_KEY, JSON.stringify(cache));
+    } catch {
+      // QuotaExceededError — 캐시 저장 실패 무시
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/feed/Feed.tsx` at line 239, Wrap the
sessionStorage.setItem(FEED_CACHE_KEY, JSON.stringify(cache)) call in a
try-catch and handle QuotaExceededError by either pruning the cache (limit
number of posts or strip/shorten contentUrls) before retrying or by falling back
to no-op (skip caching) and logging the failure; apply the same try-catch +
graceful fallback to the scroll persistence sessionStorage.setItem usage as well
so storage quota failures do not crash the page.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/common/Post/PostFooter.tsx`:
- Line 36: Import the missing hook from react-router-dom and add it to the
top-level imports so useNavigate() is defined; specifically, update the imports
in PostFooter.tsx to include useNavigate (used where const navigate =
useNavigate()) alongside the other React/third-party imports so the
ReferenceError is resolved.

In `@src/pages/feed/Feed.tsx`:
- Around line 226-250: The current guard in the useEffect uses
totalFeedPosts.length === 0 which prevents caching when only myFeedPosts are
loaded; update the condition to check the currently activeTab (activeTab) and
only bail out when the relevant list for that tab is empty (e.g., if activeTab
=== '전체' require totalFeedPosts.length > 0, if activeTab === '내 피드' require
myFeedPosts.length > 0), keeping other guards (initialLoading, tabLoading)
intact so sessionStorage.setItem(FEED_CACHE_KEY, ...) runs when the active tab's
data is present.

---

Nitpick comments:
In `@src/pages/feed/Feed.tsx`:
- Around line 33-43: getInitialFeedCache currently returns null for expired
caches but leaves the stale entry in sessionStorage; update the function
(getInitialFeedCache) to remove the expired item by calling
sessionStorage.removeItem(FEED_CACHE_KEY) when the TTL check fails (i.e., when
Date.now() - cache.timestamp >= FEED_CACHE_TTL) so the stale data is cleaned up
before returning null; keep the existing try/catch behavior for parse errors.
- Around line 148-158: The useCallback for loadMoreFeeds is missing
loadTotalFeeds and loadMyFeeds in its dependency array which can cause stale
closures and lint warnings; update the dependency array of the useCallback that
defines loadMoreFeeds to include loadTotalFeeds and loadMyFeeds, or if those
functions are unstable, memoize them (e.g., wrap loadTotalFeeds/loadMyFeeds with
useCallback where they are declared) so loadMoreFeeds remains correct and
lint-clean.
- Line 239: Wrap the sessionStorage.setItem(FEED_CACHE_KEY,
JSON.stringify(cache)) call in a try-catch and handle QuotaExceededError by
either pruning the cache (limit number of posts or strip/shorten contentUrls)
before retrying or by falling back to no-op (skip caching) and logging the
failure; apply the same try-catch + graceful fallback to the scroll persistence
sessionStorage.setItem usage as well so storage quota failures do not crash the
page.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fa6d152 and 16847e7.

📒 Files selected for processing (4)
  • src/components/common/Post/PostBody.tsx
  • src/components/common/Post/PostFooter.tsx
  • src/pages/feed/Feed.tsx
  • src/pages/feed/FeedDetailPage.tsx

@ho0010 ho0010 changed the title 피드 UX 개선 fix: feed UX 개선 Feb 25, 2026
@ho0010 ho0010 changed the title fix: feed UX 개선 fix: feed 상세 게시글 UX 저하 Feb 25, 2026
@ho0010 ho0010 changed the title fix: feed 상세 게시글 UX 저하 fix: feed 상세글 진입시 UX 저하 Feb 25, 2026
로딩 중 return <></>로 아무것도 렌더하지 않아
부모 Layout의 흰 배경이 노출되면서 깜빡임이 발생하던 문제 수정

- loading 상태: <Wrapper> + LoadingSpinner를 렌더해 어두운 배경 유지
- error / !feedData 상태: 세 개로 분리된 return <></> 를 <Wrapper /> 하나로 통합
@ho0010 ho0010 changed the title fix: feed 상세글 진입시 UX 저하 피드 UX 개선 게시글 이동 방식 변경 및 스크롤 위치 복원 Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant