Skip to content

refactor: 목록을 통계 없이 prerender 하고 조회수를 브라우저에서 끼움 - #126

Merged
Wisesaturn merged 3 commits into
feature/#122from
refactor/#119
Sep 27, 2026
Merged

Wisesaturn merged 3 commits into
feature/#122from
refactor/#119

Conversation

@Wisesaturn

Copy link
Copy Markdown
Owner

Pull Request Type

  • feat: 기능 구현 (새로운 요구사항 구현, 기존 기능 개선)
  • fix: 버그 수정
  • refactor: 코드 리팩토링

    성능 개선, 구조 변경, 기능 수정 없을 때

  • style: 포매팅, 린트 (기능 변화 없음)
  • chore: 환경설정, 패키지 관리, 라이브러리 설치 등
  • docs: 문서 작성

관련 이슈

refs #119

작업 내용

목록도 미리 만들어 둠

  • 글, 스니펫, 프로젝트 목록을 상세처럼 빌드할 때 미리 만들어 둡니다. 목록을 여는 요청이 서버를 거치지 않습니다.
  • 목록의 검색, 카테고리, 정렬은 이미 브라우저에서 처리해서 목록을 한 벌만 만들어도 그대로 동작합니다.
  • 새 글을 발행하면 다시 배포되어 목록에 들어가는 흐름은 같습니다.

조회수는 목록을 연 뒤 받아 옴

  • 목록 페이지에는 숫자를 넣지 않고, 연 뒤 목록 전체의 조회수를 한 번에 받아 카드마다 끼웁니다. 받는 동안은 숫자 자리에 skeleton 이 보이고, 받지 못하면 – 가 보입니다.
  • 목록의 숫자는 1분까지 늦을 수 있습니다. 방문이 몰려도 저장소를 자주 읽지 않기 위해서입니다.

조회순 정렬은 조회수를 받은 뒤에

  • 정렬 메뉴의 "조회수" 는 조회수를 받기 전까지 작은 스피너로 보이고 고를 수 없습니다. 받으면 고를 수 있습니다.
  • 조회순 주소로 바로 들어오면 받기 전에는 최신순으로 보이다가, 받으면 조회순으로 다시 정렬됩니다.

검증

  • 타입 검사, lint, 테스트 334개, 프로덕션 빌드 통과. 새 spec: postStatKey, sortPosts 의 조회수 받기 전 동작
  • 빌드 산출물에 세 목록 HTML 이 있고, 목록과 상세의 .data 에 views 가 없다
  • 브라우저에서 목록 통계 요청을 멈추거나(skeleton·스피너), 실패시키거나(재시도 3번 뒤 –), 그대로 두어(숫자·조회수 선택) 확인했다. 조회순에서 조회수가 높은 글이 앞에 오고 콘솔 에러가 없다
  • 행 key 를 index 대신 경로 키로 바꿨다. 같은 Notion 페이지를 두 카테고리에 발행한 문서가 있어 index 가 겹쳤고, 통계가 도착해 다시 그릴 때 숫자가 갱신되지 않았다(LOCAL 카테고리가 보이는 개발 서버에서만)

- 📋 작업 내역

  - `postDocument`, `snippetDocument`, `projectDocument`: #117 이 `default(0)` 으로 남긴 `views` 를 지웠다
  - `entities/post/config/dummy.ts`: 더미 글의 `views` 를 뺐다
  - `features/publish/api/create*.ts`: `Omit<I*, 'views'>` 를 `I*` 로 되돌렸다. 스키마에 `views` 가 없다
  - `entities/post/lib/postStatKey.ts` (+spec): 목록 행으로 `카테고리/문서 ID` 통계 키를 만든다
  - `entities/snippet/lib/snippetStatKey.ts`: 스니펫 통계 키(문서 ID)
  - 두 슬라이스 `index.ts` 에서 키 함수를 내보낸다

- 💡 설계 이유

  - 조회수의 원본은 #117 에서 `stats` 로 옮겼다. 콘텐츠 스키마에 `views` 가 남아 있으면 목록과 상세의 HTML, `.data` 에 옛 숫자가 구워진다. 지우면 두 곳 모두 숫자가 사라지고, 화면은 통계 API 값만 쓴다
  - 목록 행에는 문서 ID 가 없고 `plain_title`(스니펫은 `plainTitle`)이 있다. 발행이 공백을 `-` 로 바꿔 문서 ID 를 만들고, 상세 URL 과 이전 스크립트도 같은 계산이라 이 계산을 한곳에 둔다. 어긋나면 목록 숫자가 에러 없이 0 으로 보이므로 spec 으로 고정했다
- 📋 작업 내역

  - `features/view-count/model/useListStats.ts`: `GET /api/stats/:kind` 를 한 번 부르고 `isReady`, `views(key)`, `viewsOf(key)` 를 준다
    - `views` 는 받는 중 `undefined`, 받지 못함 `null`, 통계가 없는 콘텐츠 0
  - `PostRow`, `SnippetCard`: `views` 를 prop 으로 받아 `StatCount` 로 그린다. 스토리에 받는 중과 실패 상태를 더했다
  - `PostsPage`, `PostList`, `SnippetList`: 목록 통계를 연결한다
  - `sortPosts`, `filterPosts` (+spec): 조회순은 `viewsOf` 를 받아 정렬하고, 없으면 최신순으로 둔다
  - `Dropdown`: `pendingItems` 를 받아 그 항목 자리에 작은 스피너를 두고 누를 수 없게 한다 (`role="status"`)
  - `PostFilter`: `viewsReady` 가 거짓이면 "조회수" 를 `pendingItems` 로 넘긴다. 목록은 `VIEW_SORT_FILTER` 로 `sortOptions` 에 둔다
  - `PostList`: 행 key 를 `post.index` 에서 `postStatKey(post)` 로 바꿨다

- 💡 설계 이유

  - 목록 HTML 에는 숫자가 없으므로(#119) 조회순은 통계를 받은 뒤에만 할 수 있다. 사용자가 받기 전에는 드롭다운에서 조회순 자리에 작은 스피너를 보이고, `useQuery` 가 성공하면 조회순이 나오게 하자고 했다
    - `?orderby=mostView` 로 들어오면 받기 전에는 최신순으로 보이다가 받으면 조회순으로 다시 정렬된다
  - 목록은 사람마다 같은 값이라 `useQuery` 로 받고 `statsQueries.list` 의 `staleTime`(60초)을 API 캐시와 맞췄다. 실패하면 TanStack Query 기본 재시도(3번) 뒤 `–` 로 바뀐다. 사용자와 기본값 그대로 두기로 했다
  - 행 key 변경은 이번에 드러난 기존 버그다. 같은 Notion 페이지를 LOCAL 카테고리와 운영 카테고리에 모두 발행한 문서가 남아 `index` 가 4건 겹쳤다. 목록이 한 번 그려지고 바뀌지 않을 때는 드러나지 않았는데, 통계가 도착해 다시 그리자 React 가 겹친 행을 갱신하지 못해 숫자가 skeleton 인 채로 남았다. LOCAL 카테고리는 개발 서버에서만 보여 운영 목록에는 영향이 없었다
  - 확인: 브라우저에서 통계 요청을 멈추거나 실패시키거나 그대로 두어 skeleton·스피너 / 재시도 뒤 `–` / 숫자와 조회수 선택이 나오는 것을 봤다. 조회순에서 조회수 1 인 글이 맨 앞에 오고 콘솔 에러가 없다
- 📋 작업 내역

  - `react-router.config.ts`: `prerender` 에 `/posts`, `/snippets`, `/projects` 를 더하고 주석을 고쳤다
  - `posts._index`, `snippets._index`, `projects._index`: `headers` export 와 `LIST_CACHE_CONTROL` 을 지우고 loader 가 값만 돌려준다
  - `commons/config/cache.ts`: `LIST_CACHE_CONTROL` 을 지우고 `DETAIL_CACHE_CONTROL` 만 남겼다

- 💡 설계 이유

  - 목록은 SSR 이고 CDN 에 10분 캐시했다. 설정 주석에는 "쿼리스트링 필터 때문에 굽지 않는다" 고 적혀 있었지만, 필터와 검색은 #80 에서 브라우저로 옮겨 loader 가 쿼리스트링을 읽지 않는다. 한 벌을 구워도 된다
  - 사용자가 목록까지 CDN 에 캐시하지 말고 내용만 prerender 하자고 했다. 발행 뒤에도 바뀌는 숫자는 앞 커밋의 목록 통계로 받는다. 숫자 JSON 만 60초 캐시한다
  - 발행하면 Deploy Hook 으로 다시 구워 새 글이 목록 HTML 에 들어가는 흐름은 그대로다
  - 확인: 빌드 산출물에 세 목록 HTML 이 있고, 목록과 상세의 `.data` 에 `views` 가 없다. prerender 목록에서 필터·검색·정렬이 그대로 동작한다
@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
blog Ready Ready Preview Sep 27, 2026 6:45am UTC

@Wisesaturn Wisesaturn left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Code Review

#119 완료 조건 기준으로 확인했습니다.

  • 빌드 산출물에 세 목록 HTML 이 있고 HTML 과 .data 에 조회수가 없다
  • 목록을 열면 통계 API 한 번으로 카드마다 조회수가 나타난다
  • 필터·검색·정렬이 prerender 목록에서도 동작한다
  • 통계를 받기 전에는 정렬 드롭다운에 조회순 대신 스피너, 받은 뒤 조회순으로 정렬
  • 목록 응답에 s-maxage 가 없다 (정적 파일)
  • 새 글을 발행해 재배포되면 목록에 나타난다: 흐름은 그대로지만 운영 배포 뒤에 확인한다

블로킹 이슈는 없습니다.


return useMemo(() => {
if (data) {
const viewsOf = (key: string) => data[key]?.views ?? 0;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Note

P4: 통계 문서가 없는 콘텐츠는 0 으로 보입니다

이전 스크립트를 돌리기 전 운영 데이터처럼 stats 에 키가 없으면 목록에 0 이 나옵니다. 받지 못함(–)과 구분하려는 의도라 맞는 동작입니다. 에픽 배포 직전에 migrateStats -- --write 를 돌리는 순서(#122 체크리스트)만 지키면 운영에서 0 이 보이는 일은 없습니다.

@Wisesaturn
Wisesaturn merged commit e2377e8 into feature/#122 Sep 27, 2026
3 checks passed
@Wisesaturn
Wisesaturn deleted the refactor/#119 branch September 27, 2026 06:46

This branch was successfully deployed

1 active deployment
Preview — 22f7b29a Deployed Sep 27, 2026 by vercel[bot]
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.

1 participant