Skip to content

Conversation

@rak517
Copy link
Collaborator

@rak517 rak517 commented Jan 14, 2025

요구사항

기본

  • 자유 게시판 페이지 주소는 “/boards” 입니다.
  • 전체 게시글에서 드롭 다운으로 “최신 순” 또는 “좋아요 순”을 선택해서 정렬을 할 수 있습니다.
  • 게시글 목록 조회 api를 사용하여 베스트 게시글, 게시글을 구현합니다.
  • 게시글 title에 검색어가 일부 포함되면 검색이 됩니다.

심화

  • 반응형으로 보여지는 베스트 게시판 개수를 다르게 설정할때 서버에 보내는 pageSize값을 적절하게 설정합니다.
  • next의 prefetch 기능을 사용해봅니다.

주요 변경사항

  • Next 15 버전으로 바꿔서 작업했습니다.
  • 페이지 라우터 사용하여 구현했습니다.
  • 베스트 게시글 3개: SSR로 1페이지(orderBy=like) 받아서 바로 렌더
  • 전체 게시글 10개: SSR로 1페이지(orderBy=recent)를 받아오고, 초기 렌더링
  • 무한스크롤 IntersectionObserver로 트리거

스크린샷

스크린샷 2025-01-14 17 43 31

멘토에게

  • SSR + CSR 방식을 섞어서 사용하는게 최선이라 생각해 이렇게 섞어서 구현했는데 혹시 무한스크롤로 불러와야할때 더 좋은 렌더링 방식이 있을까요?
  • 프로젝트가 규모가 더 커진다고 가정하면, 폴더 구조나 코드 분리를 어떻게 개선하면 좋을까요
  • IntersectionObserver로 무한스크롤을 구현했는데, 데이터가 많아지면 성능에 문제가 생기나요?
  • 현재 게시글을 불러오기만 하는데, 글 작성(POST), 수정, 삭제 기능까지 확장한다면 어떤 식으로 라우팅/상태 관리, 타입, 인터페이스를 구성하는 게 좋을까요?

@rak517 rak517 requested a review from Lanace January 14, 2025 08:48
@rak517 rak517 added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Jan 14, 2025
Copy link
Collaborator

@Lanace Lanace left a comment

Choose a reason for hiding this comment

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

작업하느라 고생하셨어요ㅎㅎ

잘 하시길래 조금 꼼꼼하게 본거긴 해요!
지금 하신것도 충분히 잘하신거라...ㅋㅋ

오늘 멘토링때 조금 더 보강해서 설명드릴게여!

Comment on lines +50 to +60
.nickname {
font-size: 14px;
line-height: 24px;
color: #4b5536;
}

.date {
font-size: 14px;
line-height: 24px;
color: #9ca3af;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

보면

font-size: 14px;
line-height: 24px;

위에 2개의 스타일이 공통으로 묶여서 자주 사용되고 있는것들을 보실 수 있을거에요ㅎㅎ

그럼 이걸 아에 컴포넌트로 묶는다던가 공통 스타일을 만들어볼 수 있을것같아요

image

이런식으로 묶여있으니까요ㅋㅋ!

Comment on lines +1 to +10
export const formatDate = (dateString: string): string => {
const date = new Date(dateString);
return date
.toLocaleDateString("ko-KR", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})
.replace(/\./g, ". ");
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

물론 이렇게 해도 되긴 한데, 실무에서는 moment 나 dayjs 같은걸 좀더 많이 쓰긴 해요!

const value = dayjs(dateString);

return value.format("YYYY.MM.DD");

이런 느낌으로 쓸 수 있거든요ㅎㅎ

물론 지금 작성하신 코드도 맞아요..!

import { formatDate } from "@/utils/formattedDate";

export function ArticleItemCard({ title, content, image, likeCount, writer, updatedAt }: Article) {
const formattedDate = formatDate(updatedAt);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Format유틸로 잘 분리하셨네요ㅎㅎ!
굳굳

이런식으로 분리할 수 있으면 분리시켜주는게 좋고, 유틸성은 따로 관리하는게 좋아요!

Copy link
Collaborator

Choose a reason for hiding this comment

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

조금더 개선한다면... useMemo를 쓰는것도 좋을것같네요!

  const formattedDate = useMemo<string>(() => {
  return formatDate(updatedAt);
}, [updatedAt]);

import Image from "next/image";
import { formatDate } from "@/utils/formattedDate";

export function ArticleItemCard({ title, content, image, likeCount, writer, updatedAt }: Article) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

이렇게 하면 ArticleItemCard 를 사용하는쪽이 이렇게 되어야해요

<ArticleItemCard title={item.title} content={item.content} ... />

물론 이렇게 할수도 있긴 하겠죠

<ArticleItemCard {...item} />

근데 아에 타입을 따로 정의하면 좀더 사용하는사람에게 명시적으로 전달할 수 있어요

interface ArticleItemCardProps {
  article: Article
}

...

<ArticleItemCard article={item} />


interface ArticleListProps {
initialArticles: FetchArticlesResponse;
initialOrder: "recent" | "like";
Copy link
Collaborator

Choose a reason for hiding this comment

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

좋네요ㅎㅎ! 딱 이거 2개만 올 수 있도록 처리하면 실수로 잘못된 값이 들어오는걸 막을 수 있을거에요!

const [hasMore, setHasMore] = useState(true);

// IntersectionObserver
const observerTarget = useRef<HTMLDivElement | null>(null);
Copy link
Collaborator

Choose a reason for hiding this comment

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

잘 하신것같아요!
observer가 동작하는 방식은 멘토링떄 같이 설명드릴게요.

그리구 성능에 그렇게 무리가 되진 않아요ㅎㅎ!

const observer = new IntersectionObserver(onIntersect, { threshold: 0.1 });
observer.observe(observerTarget.current);

return () => observer.disconnect();
Copy link
Collaborator

Choose a reason for hiding this comment

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

disconnect 시켜주는 부분까지 꼼꼼하시네요!

꼭 이벤트를 걸어줬다면 이벤트를 해지하는것도 쌍으로 처리해주세요~ㅋㅋ
useEffect에서요

Comment on lines +133 to +137
<div className={style.all_section}>
{articles.map((article) => (
<ArticleItemCard key={article.id} {...article} />
))}
</div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

데이터가 없을떄도 같이 처리해주면 좋을것같아요ㅎㅎ

Comment on lines +12 to +15
<div className={style.post_header}>
<h3>게시글</h3>
<button onClick={handleClick}>글쓰기</button>
</div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

페이지 이동하는건 가급적이면 router를 쓰는것보단 Link를 쓰는게 좋아요ㅠ

미리 이동하기 전에 prefetching 을 하거든요ㅎㅎ
성능상 좋아요

Comment on lines +8 to +13
}: {
page?: number;
pageSize?: number;
orderBy?: "like" | "recent";
keyword?: string;
}): Promise<FetchArticlesResponse> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

이것도 별도의 Request Type을 만들어주면 사용하기 편하실꺼에요ㅎㅎ!

@Lanace
Copy link
Collaborator

Lanace commented Jan 17, 2025

SSR + CSR 방식을 섞어서 사용하는게 최선이라 생각해 이렇게 섞어서 구현했는데 혹시 무한스크롤로 불러와야할때 더 좋은 렌더링 방식이 있을까요?

SSR과 CSR일떄 장단점을 생각해보시면 좋을것같아요ㅎㅎ!
아니면 다른 서비스들은 어떻게 하고있는지 찾아보는것도 좋구요!

아마 CSR을 하고 있는 곳들이 조금 더 많을것같아요.
왜냐하면 어차피 계속해서 추가적으로 아이템들을 로드해야 하기때문에 어차피 로드할꺼면 Client 쪽에서 처리하는게 일괄적으로 관리할 수 있어서 좋을꺼에요!ㅎㅎ

@Lanace
Copy link
Collaborator

Lanace commented Jan 17, 2025

프로젝트가 규모가 더 커진다고 가정하면, 폴더 구조나 코드 분리를 어떻게 개선하면 좋을까요

지금 분리한 정도도 나름 잘 정리가 되어있다고 생각해요!
근데 보통은 도메인 단위로 나누기도 하고, 페이지 단위로 나누기도 해요ㅎㅎ

정답이 있는건 아닌데, 나눴을때 균질하게 나뉘는지, 정리가 잘 되는지 정도를 보고 판단해보시면 좋을것같아요!

@Lanace
Copy link
Collaborator

Lanace commented Jan 17, 2025

IntersectionObserver로 무한스크롤을 구현했는데, 데이터가 많아지면 성능에 문제가 생기나요?

이부분은 딱히 성능상 문제가 있진 않을텐데, 라이브러리에 다른것들은 뭐가 있는지 보시는것도 좋을것같아요ㅎㅎ!

@Lanace
Copy link
Collaborator

Lanace commented Jan 17, 2025

현재 게시글을 불러오기만 하는데, 글 작성(POST), 수정, 삭제 기능까지 확장한다면 어떤 식으로 라우팅/상태 관리, 타입, 인터페이스를 구성하는 게 좋을까요?

2가지가 있을텐데, 하나는 브라우저에서 직접 서버 API를 호출할 수 있구요,
다른 하나는 Nextjs 서버를 호출하고 Next서버에서 API 서버를 호출하는 Server to Server 통신방법이 있을것같아요.

근데 저는 가급적이면 그떄마다 request / response 타입을 정의해서 쓰는걸 좀 더 선호하긴 해요ㅎㅎ!

@Lanace Lanace merged commit 8ea8f86 into codeit-bootcamp-frontend:Next-최성락 Jan 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants