Skip to content

Conversation

@ToKyun02
Copy link
Collaborator

@ToKyun02 ToKyun02 commented Jan 10, 2025

요구사항

기본

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

심화

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

주요 변경사항

  • Next.js 프레임워크를 이용한 boards 페이지를 구현했습니다.
  • Next 15버전, 앱 라우터를 사용하여 구현했습니다.
  • 게시글이 로딩 중일 땐 스켈레톤 UI를 보여주도록 구현했습니다.

스크린샷

/boards 페이지

boards 페이지 캡처

image

페이지 초기 접속 요청에 따른 HTML 파일 응답 캡처

image

추가 예정 사항

  • 9주차 트러블 슈팅 정리

멘토에게

  • 9주차 트러블 슈팅 내용은 README.md에 정리했습니다.
  • 베스트 게시글은 매번 새롭게 만들 필요가 없다고 판단하여 ISR로 구현했고, revalidate 시간은 60초입니다.
    • 본래 베스트 게시글은 반응형에 따라 api 요청으로 구현했었는데, 어차피 변화가 거의 없기도 하고, 베스트 게시글의 최대값이 3으로 고정되어있기에 서버에 보내는 pageSize값을 설정하는 방식에서 css로 hidden처리하는 방법으로 바꿨습니다.
  • 전체 게시글은 잦은 업데이트가 필요하다고 생각하여 SSR로 미리 개수를 생성하고 하이드레이션 하는 방법과 CSR로 처음부터 불러오는 방식 중 고민하다가 후자로 택하여 구현했습니다.
  • 검색 Input filed에서 Enter를 입력하면 쿼리스트링 값을 이용하여 전체 게시글 부분을 검색 결과 리스트로 나오도록 구현했습니다.
    • 사용자가 해당 페이지의 쿼리스트링을 직접 입력해도 적용됩니다.

@ToKyun02 ToKyun02 requested a review from Lanace January 10, 2025 04:31
@ToKyun02 ToKyun02 self-assigned this Jan 10, 2025
@ToKyun02 ToKyun02 added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Jan 10, 2025
Copy link
Collaborator

Choose a reason for hiding this comment

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

https://gitignore.io/ 같은 사이트 참고해서 git ignore 적용해보시는것도 좋을것같아요ㅎㅎ!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

기본적으로 next 설치 시 생성되는 ignore를 썼는데, 위 사이트도 참고해볼게요!

Copy link
Collaborator

Choose a reason for hiding this comment

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

d.ts 파일은 딱히 만들 필요는 없을꺼에요

그냥 .ts 파일을 만들고 export 하는게 좀 더 좋아요

d.ts파일은 javascript 파일과 typescript 파일을 섞어쓸때 정의하기 위해서 사용하는 파일이거든요ㅎㅎ!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵 감사합니다! 수정하도록 하겠습니다.

if (i === 1) hidden = 'hidden md:flex';
else if (i === 2) hidden = 'hidden pc:flex';

return <BestArticle key={article.id} article={article} hidden={hidden} />;
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기서 사용하는 hidden 이 그냥 className이랑 크게 다른것같진 않은데 맞나여??

따로 hidden이라는 이름을 쓰신 이유가 궁금해서요ㅠ

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

className이 맞습니다! 이 부분 수정하겠습니다.

<p>{article.writer.nickname}</p>
<div className='flex items-center gap-1'>
<Image src='/assets/icons/heart_empty.svg' alt='빈 하트 이미지' width={16} height={16} style={{ width: 16, height: 16 }} />
<p>{article.likeCount}</p>
Copy link
Collaborator

Choose a reason for hiding this comment

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

<p>{article.likeCount.toLocaleString()}</p> 

위와같이 노출하면 1000 -> 1,000 으로 노출되어서 보기가 좀더 좋을꺼에요~ ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Figma에는 천단위 구분자를 안넣어서 따로 넣진 않았는데, 참고하겠습니다!


export async function getArticles(query: ArticlesParam = {}, option: RequestInit = {}): Promise<{ list: Article[]; totalCount: number }> {
const queryParam = new URLSearchParams(Object.entries(query)).toString();
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/articles?${queryParam}`, option);
Copy link
Collaborator

Choose a reason for hiding this comment

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

오... 이렇게 환경변수로 관리하면 매우 좋아요ㅎㅎ!

나중에 서버의 host가 바뀌었을떄 관리하기가 좋거든요ㅋㅋ

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

감사합니다!

Comment on lines +10 to +33
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'sprint-fe-project.s3.ap-northeast-2.amazonaws.com',
},
{
protocol: 'https',
hostname: 'example.com',
},
{
protocol: 'https',
hostname: 'mblogthumb-phinf.pstatic.net',
},
{
protocol: 'https',
hostname: 'ibb.co',
},
{
protocol: 'https',
hostname: 'flexible.img.hani.co.kr',
},
],
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

굳굳

필요한거만 이렇게 열어두는게 좋아요ㅎㅎ!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

모든 도메인 허용하려 했다가 그건 좀 아닌 것 같아서 패턴 넣었는데 잘한 것 같네요 ㅎㅎ

Comment on lines +5 to +29
function SkeletonUi({ cnt }: { cnt: number }) {
return (
<>
{Array.from({ length: cnt }).map((_, i) => (
<div key={i} className='flex flex-col gap-6 rounded-lg p-6 bg-gray-100'>
<div className='w-[102px] h-[30px] bg-gray-200'></div>
<div className='flex gap-2 justify-between'>
<div className='w-[256px] h-[64px] bg-gray-200'></div>
<div className='w-[72px] h-[72px] bg-gray-200'></div>
</div>
<div className='flex justify-between'>
<div className='flex gap-4'>
<div className='w-14 h-6 bg-gray-200'></div>
<div className='flex items-center gap-1'>
<div className='w-3.5 h-3.5 bg-gray-200'></div>
<div className='w-12 h-4 bg-gray-200'></div>
</div>
</div>
<div className='w-20 h-4 bg-gray-200'></div>
</div>
</div>
))}
</>
);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

오 스켈레톤 UI까지 구현하셨군여ㅋㅋ
이런 디테일 매우 좋아요ㅎㅎ!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

감사합니다!

import dayjs from 'dayjs';
import { useState } from 'react';

const DEFAULT_IMAGE = '/assets/icons/profile.svg';
Copy link
Collaborator

Choose a reason for hiding this comment

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

기본 이미지까지 잘 처리해주셨네요ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

감사합니다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

이건 지워도 되는거죠...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

향후 홈페이지 작업 때문에 남겨놨습니다!

- 기존에 CSR로만 프로젝트를 구현했었기에 서버 컴포넌트를 프로젝트를 시작하면서 정의하기엔 생각보다 쉽지 않았다.
- 그래서, 우선 리액트에서 사용했던 방식인 CSR로 먼저 MVP를 만들었고, 그 후에 서버 컴포넌트 전환이 필요한 컴포넌트가 무엇인지 고민했었다.
- 베스트 게시글의 경우 여타 웹사이트처럼 사용자에 요청에 따라 반드시 최신의 데이터를 보장할 필요가 없다고 판단했고, 사용자의 상호작용 또한 불필요하다고 생각하여 ISR로 전환하였다.
- 전체 게시글의 경우 정적인 것보다 동적으로 게시글 목록을 부여해야 한다고 판단하여 SSR + 하이드레이션, CSR 방식 중 고민했는데, 어차피 둘다 추가적인 데이터를 클라이언트 측에서 요청을 해야 한다고 생각하여 기존에 리액트에서 사용했던 방식인 CSR로 구현하였다.
Copy link
Collaborator

Choose a reason for hiding this comment

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

이부분은 app router 에 loading.tsx 를 활용해볼수도 있을것같아요!

스켈레톤 적용하신 부분들이 있던데, 이런 부분들을 따로 관리할 수 있거든요ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suspense 컴포넌트의 fallback으로 활용했었는데, loading.tsx도 고려해보겠습니다!

Comment on lines +25 to +28
### 왜 axios를 안쓰고 fetch로 데이터를 불러오는가
- 기존 리액트에선 fetch보다 axios라이브러리를 쓰면 조금 더 쉽고, 짧게 데이터를 불러올 수 있었다.
- next 프로젝트에선 왜 axios보다 fetch를 더 많이 쓰는 지 궁금해서 찾아본 결과, 본래 자바스크립트의 fetch 메서드에서 추가적인 기능이 있다는 걸 알게 되었다.
- 그 추가적인 기능이 데이터 캐싱이었고, revalidate 설정, tag기반 캐싱, 강제 캐싱 등 여러 옵션을 설정할 수 있었는데, 이러한 캐싱 설정을 간편하게 할 수 있다는 이유로 fetch를 더 많이 쓰던 것이었다.
Copy link
Collaborator

Choose a reason for hiding this comment

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

이거는 조금 말이 많긴 해요ㅎㅎ;;

어차피 캐싱은 react-query 같은 곳에서도 해주기도 하고, 그게 그렇게 큰 비용이 아니라고 생각되어서...

경험상 60:40 으로 axios를 조금더 많이 사용하는듯 싶어요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

조언 감사합니다! 상황에 따라 써야겠네요


- 요구사항에 모든 버튼에 대해 자유롭게 Hover 효과를 적용하라고 되어있었다.
- Heart 아이콘에 Hover 효과를 부여해서 배경색을 변화시키고 싶었는데, 찾아보니깐 svg 파일을 리액트 컴포넌트로 변환시킬 수 있는 걸 알게 되었다.
- 리액트 컴포넌트로 변환시킨 svg 컴포넌트가 hover 됐을 때 fill속성에 값을 줌으로써 배경색을 채웠다.
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

@Lanace Lanace merged commit 4c3ccc6 into codeit-bootcamp-frontend:Next-김도균 Jan 16, 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.

2 participants