-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[6주차] Team Buldog 김다희 & 이지인 미션 제출합니다. #15
base: master
Are you sure you want to change the base?
Conversation
feat : 초기 설정
style : global font
feat : tawilwind css, prettier setting
This reverts commit 3d2679a.
Fix tailwind
feat : 이미지 클릭 시 해당 영화 detail로 이동
utils -> movieAPI에 fetch 함수 이를 main 페이지에서 이용해서, 영화 정보 불러오기 클라이언트 컴포넌트인 search 페이지에서 api를 통해, 검색어에 따른 영화정보 불러올 수 있도록 구현
feat : api 1개로, main 페이지 서버 컴포넌트로 변경
fix : 검색 페이지 영화 중복으로 뜨는 문제, 이미지 url 200번대 요청만 필터링
# Conflicts: # src/app/search/page.tsx
search에서 api 요청보내는 것과 - 직접 주소창에 /api url 입력하는것 fetch 인자에 header 추가해서 구분 -> middleware에서 이 header에 따라 api 실행 or rewrite할지 처리
feat : 미들웨어 추가, 404,403,각종 페이지
이를 위해 wrapper client- component인 movieindex 추가 이유 - main 페이지는 서버 컴포넌트로 유지하기 위해서, hook을 사용하지 못하므로 클라이언트 컴포넌트로 MovieINdex 컴포넌트 추가 (캐러셀, 컨트롤러 부모 컴포넌트)
feat : 변하는 캐러셀 영화에 맞게, 영화 소개도 업데이트 되도록 수정
# Conflicts: # src/app/components/Carousel.tsx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지난주보다 발전한 코드를 볼 수 있었던 거 같아요~ 고생 많으셨습니다 !
useEffect(() => { | ||
const fetchData = async () => { | ||
try { | ||
const response = await fetch('/api',{ | ||
headers: { | ||
'X-Client-Request': 'true' | ||
// 클라이언트에서 요청하는 것을 나타내는 헤더 추가(직접 주소창에 입력하는 것과 구분) | ||
}}); | ||
const data: Movie[][] = await response.json(); | ||
|
||
//중복 제거 | ||
const flatData = data.flat(); | ||
const filteredMovies = removeDuplicates(flatData); | ||
|
||
setAllMovies(filteredMovies); | ||
} catch (error) { | ||
console.error('Failed to load movies:', error); | ||
} | ||
}; | ||
|
||
fetchData(); | ||
}, []); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
search 페이지는 검색 이벤트 때문에 클라이언트 컴포넌트여야 하므로, 프로젝트 서버 이용(search를 위한 api 1개만 남김) 필요
라고 적어두셨는데 nextjs 서버를 이용하든 안하든 클라이언트 컴포넌트 서버 컴포넌트로 구현하는 거랑은 무관하다구 알고있는데 어떤 점에서 이렇게 말씀하신 건지 궁금합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Search 컴포넌트를 서버 컴포넌트로 둬서 데이터 패칭을 하고, 해당 데이터를 프롭으로 하위 컴포넌트인 클라이언트 컴포넌트에 넘겨준 뒤 그 클라이언트 컴포넌트에서 사용자 인터랙션이 있는 코드를 작성할 수도 있을 거 같아요~!
<h2 className='text-[26.75px] font-bold my-[18px] ml-[10px]'>Top Searches</h2> | ||
<div className='h-[573px] overflow-y-auto'> | ||
{inputValue.trim() === '' | ||
? allMovies.map((movie, index) => <SearchLists key={index} movie={movie} />) | ||
: allMovies | ||
.filter((movie) => movie.title.toLowerCase().includes(inputValue.toLowerCase())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
index를 key로 사용하는 것보다 movie.id로 변경하는 게 좋을 거 같아요
[React] 배열의 index를 key로 쓰면 안되는 이유
const accessibleMovies = await Promise.all( | ||
data.results.map(async (movie: Movie) => { | ||
const imagePath = `https://image.tmdb.org/t/p/original${movie.backdrop_path}`; | ||
const isImageOk = await checkImage(imagePath); // 접근 가능한 이미지인지 체크하기 | ||
return isImageOk ? movie : null; | ||
}), | ||
); | ||
// null이 아닌 것만 반환 | ||
return accessibleMovies.filter((movie: Movie | null) => movie !== null); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이미지 에러가 나는 영화들에 대해 이런 식으로 처리를 해주셨군요! 그런데 에러가 나는 이미지에 대한 검색 결과는 아예 보여주지 않아서 기본 이미지를 보여주는 식으로도 처리할 수 있을 거 같아요~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const BASE_URL = 'https://api.themoviedb.org/3/movie';
const IMAGE_BASE_URL = 'https://image.tmdb.org/t/p/original';
이렇게 상수로 관리해두면 url이 바뀌었을 때 이 값만 고치면 돼서 편하답니다~ 다른 곳에서도 이 값을 사용해서 constant 폴더에 따로 둬도 좋을 거 같아요 ㅎㅎ
//image URL 접근 가능 여부 체크 함수 | ||
const checkImage = async (url: string): Promise<boolean> => { | ||
try { | ||
const res = await fetch(url, { method: 'HEAD' }); | ||
return res.ok; // code가 200번대면, 성공적 - >해당 이미지 반환 | ||
} catch (error) { | ||
console.error(' 이미지 접근 불가 ', error); | ||
return false; // 이미지 접근 불가 | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
덕분에 head 메소드가 의미하는 바에 대해 처음 알게됐어요 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
환경변수 냠
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.gitignore
에 .env
파일을 입력해서 push를 방지해주시면 될 것 같아요.
저희팀이 이번 과제에서 참고한 글 남길게용
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 관련 내용 공부한거 남기고 갑니다!!
- 타인에게 보여주기 민감한 정보를 담고 있는 .env.local 파일
- .gitignore에 이 파일이 자동으로 등록되어 있다. 즉 깃허브에 push 해도 .env.local 파일은 올라가지 않는다.
- .gitignore에 등록되어 있는 파일이기 때문에 깃허브 협업 시 누군가가 .env.local을 작성하고 깃허브에 push해도 다른 팀원들은 확인할 수 없다. 각자 로컬에서 .env.local 파일을 작성해주어야 한다.
- vercel로 배포 시 vercel은 .gitignore 파일을 적용하지 않기 때문에 .env.local 파일도 적용되지 않는다. 따라서 vercel에서 따로 환경 변수를 등록해주어야 한다. (참고 자료)
src={movieDetail.backdrop_path ? url : ''} | ||
alt="movie poster" | ||
fill | ||
sizes="100vw" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vw는 뷰포트, 전체 화면 너비를 따라가는 거라 sizes를 100vw로 설정해두는 것보다 375px로 지정해두는 게 적절할 거 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Buldog팀 안녕하세요!
이번 과제에서 섬세하게 에러 페이지와, 나머지 구현하지 않아도 되는 페이지들을 이모티콘과 함께 띄워주시는 등.. 메인페이지에서 영화 rank에 따라 바뀌는 부분도 재밌게 봤어요! 주석도 쉽게 작성해주셔서 코드 보는 데 도움이 되었습니다... 👍
시험기간 화이팅하시규.. 다음 과제도 화이팅해용!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.gitignore
에 .env
파일을 입력해서 push를 방지해주시면 될 것 같아요.
저희팀이 이번 과제에서 참고한 글 남길게용
]; | ||
|
||
const pathname = usePathname(); | ||
console.log(pathname); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
보통 커밋할 때 console.log는 삭제하고 하는 것이 일반적이라고 합니다!
|
||
export default CommingSoon; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다른 페이지들도 센스 있게 아이콘으로 띄워주시네용 👍
function Carousel({ movies, currentMovieIndex }: CarouselProps) { | ||
return ( | ||
<div className="relative mb-[20px] h-[395px] w-[375px] "> | ||
<div className="w-[375px] h-[415px] overflow-hidden relative"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
최외곽 div
랑 내부 이미지 감싸는 div
가 height이 오타난 것 같아요. 어차피 fill 속성으로 부모 크기에 따라 맞춰지게 지정해주셔서 이미지를 감싸는 div
에 따로 width와 height을 지정해주지 않아도 될 것 같아요!
return ( | ||
<div className="mr-[7px] flex-shrink-0" key={movie.id}> | ||
<div style={{ width: 103, height: imageSize }}> | ||
{movie.backdrop_path && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
backdrop_path가 존재하는지 위에 if문과 함께 두 번 검사하고 있는 것 같아요.
이미 위에서 if문으로 만약 backdrop_path가 없는 경우, 해당 아이템을 렌더링하지 않고 넘어가도록 해서 movie.backdrop_path $$ (
이 부분은 없어도 될 것 같아요!
</MovieIndex> | ||
)} | ||
{movies.map((movieList, index) => ( | ||
<div key={index}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
배열의 인덱스를 키 값으로 사용하는 것은 지양해야 한다고 합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
보통 movie.id를 많이 쓰는 것 같아요 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이번주 과제도 수고많으셨어요 !! UI도 깔끔하고 리팩토링도 열심히 하신것 같아요 ㅎㅎ 트러블 슈팅도 잘 봤습니당 !! 👍
isCircular: boolean; | ||
} | ||
|
||
const MainLists: React.FC<MainListProps> = ({ movies, itemClass, isCircular }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
React.FC의 사용을 최소화하기 위해서
기존의 React.FC<MainListProps> = (props) => {};
를
(props: MainListProps) => {};
요렇게 바꿀 수도 있답니다 !!
</MovieIndex> | ||
)} | ||
{movies.map((movieList, index) => ( | ||
<div key={index}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
보통 movie.id를 많이 쓰는 것 같아요 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
404에러 처리까지 !! 멋져요
"react": "^18", | ||
"react-dom": "^18", | ||
"react-lottie-player": "^2.0.0", | ||
"react-slick": "^0.30.2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
react-slick라이브러리를 쓰셨구나 !! 최고입니다 !! 저도 다음에 써보려고요 ㅎㅎ
<div className='flex space-x-[25px]'> | ||
{ | ||
Menu.map((menu) => { | ||
return( | ||
<span className='font-normal text-header'key={menu}> | ||
{menu} | ||
</span> | ||
) | ||
} | ||
) | ||
} | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.prettierrc파일은 잘 있는데 indent 사이즈가 설정한 것과 다른것 같아요 ! prettierrc가 잘 적용이 안됐을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요 프론트 19기 멘토 김현민 이라고합니다~!!
이미 많은 분들이 코드 리뷰를 남겨준 후여서 약간의 제 의견들만 남겨봤습니다 ㅎㅎ
정답이라고 생각하시지 마시고 쭉 읽어보며 참고만 해주시면 좋을 것 같아요~~
이번 과제도 너무 고생 많으셨습니다!!! 👍
name: git push into another repo to deploy to vercel | ||
|
||
on: | ||
push: | ||
branches: [master] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
container: pandoc/latex | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Install mustache (to update the date) | ||
run: apk add ruby && gem install mustache | ||
- name: Setup Node.js | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: '20.x' # Ensuring compatibility with Node.js 20 | ||
- name: creates output | ||
run: sh ./build.sh | ||
- name: Pushes to another repository | ||
id: push_directory | ||
uses: cpina/github-action-push-to-another-repository@master | ||
env: | ||
API_TOKEN_GITHUB: ${{ secrets.DEPLOY_TOKEN }} | ||
with: | ||
source-directory: 'output' | ||
destination-github-username: jinnyleeis | ||
destination-repository-name: next-netflix-19th-deploy | ||
user-email: ${{ secrets.OFFICIAL_ACCOUNT_EMAIL }} | ||
commit-message: ${{ github.event.commits[0].message }} | ||
target-branch: master | ||
- name: Test get variable exported by push-to-another-repository | ||
run: echo $DESTINATION_CLONED_DIRECTORY |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
팀 레포에 대한 버셀의 요금 부과 정책에 대비하여 Github Actions를 활용해 개인 레포로 옮긴 것 멋집니다 ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 자동배포 설정해주셨군요!! 저희도 설정해줬는데 너무 편한 것 같아요
{ /* 그라데이션 */} | ||
<div | ||
style={{ | ||
position: 'absolute', | ||
top: 0, | ||
left: 0, | ||
right: 0, | ||
height: '40%', | ||
backgroundImage: 'linear-gradient(to bottom, rgba(0, 0, 0, 0.7), transparent)', | ||
}} | ||
></div> | ||
<div | ||
style={{ | ||
position: 'absolute', | ||
bottom: 0, | ||
left: 0, | ||
right: 0, | ||
height: '40%', | ||
backgroundImage: 'linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent)', | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그라데이션 부분은 따로 컴포넌트로 만들어서 사용해주시면 협업을 할 때 가독성이 좋아질 것 같아요!!
{ /* 그라데이션 */} | |
<div | |
style={{ | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
right: 0, | |
height: '40%', | |
backgroundImage: 'linear-gradient(to bottom, rgba(0, 0, 0, 0.7), transparent)', | |
}} | |
></div> | |
<div | |
style={{ | |
position: 'absolute', | |
bottom: 0, | |
left: 0, | |
right: 0, | |
height: '40%', | |
backgroundImage: 'linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent)', | |
}} | |
<GradientStyle /> |
<button className="flex h-11 w-28 cursor-pointer items-center justify-center gap-2.5 rounded-md border-none bg-[#C4C4C4]"> | ||
<Image src={Play} alt="play" width={18} height={21.6} /> | ||
<span className="text-play font-semibold text-[#000000]">Play</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
자주 사용되는 색깔에 대해서는 쉬운 네이밍으로 세팅하는 게 좋을 것 같아요 ㅎㅎ
https://doyourbestcode.tistory.com/131
제가 Next + Tailwind 를 세팅하는 방식도 살짝 참고만 해보세요~~
import logo from '../../public/icons/logos_netflix-icon.svg' | ||
|
||
function Header() { | ||
const Menu = ['Tv Shows' ,'Movies', 'My List'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
헤더 텍스트들을 배열로 관리해주신 덕분에 추후 유지보수에도 좋겠네요 ㅎㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그러게요! 배열로 관리할 생각은 따로 못해봤는데 좋은 방법인 것 같습니다
</div> | ||
<h2 className='text-[26.75px] font-bold my-[18px] ml-[10px]'>Top Searches</h2> | ||
<div className='h-[573px] overflow-y-auto'> | ||
{inputValue.trim() === '' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
공백 제거 디테일 좋네요~~
|
||
export default function ApiError() { | ||
return ( | ||
<div> | ||
<Head> | ||
<title>API Access Denied</title> | ||
<meta name="description" content="Access to the API is restricted" /> | ||
</Head> | ||
<main> | ||
<h1 className='text-[150px] mt-[300px] flex justify-center'>☠️</h1> | ||
<h2 className="ml-[16px] mt-[20px] text-[20.92px] font-bold flex justify-center"> 403 - API Access Denied</h2> | ||
<p className="ml-[16px] text-[20.92px] flex justify-center">You do not have permission to access this API.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
에러 페이지까지 설정해주시는 디테일 좋네요~!!
<h2 className="ml-[16px] mt-[22px] text-[20.92px] font-bold"> | ||
{mainListsTitle[index]} | ||
</h2> | ||
<MovieList | ||
movies={movieList} | ||
itemClass={index === 0 ? 'rounded-full' : ''} | ||
isCircular={index === 0} | ||
/> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요 슬라이더도 같은 디자인이므로 컴포넌트로 생성해주시면 가독성에 더 좋을 것 같네요 ㅎㅎ
const categories = ['top_rated', 'popular', 'upcoming', 'now_playing']; | ||
|
||
try { | ||
const movies = await Promise.all(categories.map(fetchMoviesData)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
병렬적으로 처리해서 netword-waterfall 방지하신 점 좋네요 ㅎㅎ
react-intersection-observer 라이브러리를 활용하여 우선 Previews, NewReleases 2개의 api만 호출해온 후, Popular on Netflix 슬라이더가 view에 보이는 순간 나머지 2개의 api를 호출하는 방식도 재밌을 것 같고 초기 성능에도 효과적일 수 있을 것 같네요~~
function Header() { | ||
const Menu = ['Tv Shows' ,'Movies', 'My List'] | ||
return ( | ||
<div className='fixed top-[24px] flex justify-center items-center w-[375px] h-[57px] z-50'> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어느정도 스크롤 하면 헤더의 background-color를 설정해준다면 스크롤이 내려왔을 때도 헤더가 더 잘 보일 것 같아요!!
export const metadata: Metadata = { | ||
title: 'next-netflix-19', | ||
description: 'ceos19', | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
메타데이터 설정까지 너무 좋습니다 ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
마지막 프론트 협동 과제 수고하셨습니다!!
name: git push into another repo to deploy to vercel | ||
|
||
on: | ||
push: | ||
branches: [master] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
container: pandoc/latex | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Install mustache (to update the date) | ||
run: apk add ruby && gem install mustache | ||
- name: Setup Node.js | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: '20.x' # Ensuring compatibility with Node.js 20 | ||
- name: creates output | ||
run: sh ./build.sh | ||
- name: Pushes to another repository | ||
id: push_directory | ||
uses: cpina/github-action-push-to-another-repository@master | ||
env: | ||
API_TOKEN_GITHUB: ${{ secrets.DEPLOY_TOKEN }} | ||
with: | ||
source-directory: 'output' | ||
destination-github-username: jinnyleeis | ||
destination-repository-name: next-netflix-19th-deploy | ||
user-email: ${{ secrets.OFFICIAL_ACCOUNT_EMAIL }} | ||
commit-message: ${{ github.event.commits[0].message }} | ||
target-branch: master | ||
- name: Test get variable exported by push-to-another-repository | ||
run: echo $DESTINATION_CLONED_DIRECTORY |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 자동배포 설정해주셨군요!! 저희도 설정해줬는데 너무 편한 것 같아요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 관련 내용 공부한거 남기고 갑니다!!
- 타인에게 보여주기 민감한 정보를 담고 있는 .env.local 파일
- .gitignore에 이 파일이 자동으로 등록되어 있다. 즉 깃허브에 push 해도 .env.local 파일은 올라가지 않는다.
- .gitignore에 등록되어 있는 파일이기 때문에 깃허브 협업 시 누군가가 .env.local을 작성하고 깃허브에 push해도 다른 팀원들은 확인할 수 없다. 각자 로컬에서 .env.local 파일을 작성해주어야 한다.
- vercel로 배포 시 vercel은 .gitignore 파일을 적용하지 않기 때문에 .env.local 파일도 적용되지 않는다. 따라서 vercel에서 따로 환경 변수를 등록해주어야 한다. (참고 자료)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
svg를 불러와서 next/image의 Image의 src에 넣어주는 방법도 좋지만 svg 파일을 컴포넌트처럼 사용할 수 있는 방법이 있더라구요!! 저도 이번에 첨 알았습니다 쓰면서 편하고 좋았어서 한번 참고해보시는거 추천드려요 ㅎㅎ
import logo from '../../public/icons/logos_netflix-icon.svg' | ||
|
||
function Header() { | ||
const Menu = ['Tv Shows' ,'Movies', 'My List'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그러게요! 배열로 관리할 생각은 따로 못해봤는데 좋은 방법인 것 같습니다
return ( | ||
<Lottie | ||
animationData={netflix} | ||
style={{ width: "60%" }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
중간중간 이렇게 style을 지정해주신 부분이 있던데 이유가 궁금해요!
<Link href={link} key={id} className="flex flex-col items-center"> | ||
{/* 이미지 색상은 안 변하므로 이 부분 해결 필요 */} | ||
<Image | ||
src={pathname === link ? clicked : src } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pathname === link
를 확인하는 부분이 반복되고 있으니까 isCurrentPath
와 같은 변수로 따로 빼서 관리해줘도 좋을 것 같아요!
<div | ||
style={{ | ||
position: 'absolute', | ||
top: 0, | ||
left: 0, | ||
right: 0, | ||
height: '40%', | ||
backgroundImage: 'linear-gradient(to bottom, rgba(0, 0, 0, 0.7), transparent)', | ||
}} | ||
></div> | ||
<div | ||
style={{ | ||
position: 'absolute', | ||
bottom: 0, | ||
left: 0, | ||
right: 0, | ||
height: '40%', | ||
backgroundImage: 'linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent)', | ||
}} | ||
></div> | ||
</> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부붕느 공통 스타일로 따로 빼서 관리해주는게 가독성에 좋을 것 같아요!
배포 링크
🔗
정적 라우팅과 동적 라우팅
정적 라우팅 (Static Routing)
URL 경로가 사전에 정의되고, 각 경로는 특정 페이지로 매핑되는 방식
/main, /search 등의 경로는 항상 각각 main, search 페이지로 매핑되고 경로는 static함
경로- 페이지 매핑은 애플리케이션 빌드 시점에 결정됨
동적 라우팅 (Dynamic Routing)
URL 경로의 일부가 동적으로 결정되는 방식
URL의 일부가 변수처럼 동작하여, 이를 통해 다양한 페이지를 렌더링 가능
Next.js에서는 대괄호([])를 사용해 동적 경로를 정의
이번 프로젝트에선
동적 라우팅을 사용하여 영화 상세 페이지를 구현함 detail[id]
사용자가 영화 선택 → 해당 영화의 id에 따라 상세 페이지로 이동
getMovieDetailsById를 통해 영화 세부 정보를 가져오는 api 요청을 보냄
이를 통해 사용자가 클릭/검색한 각각의 영화정보에 맞는 동적 페이지 구현
저번 과제에 추가 개선한 기능, 최적화
http 요청 200번대인 이미지만 필터링하는 checkImage 함수 구현
api 4개 → 1개로 수정(최적화), main 페이지 클라이언트 컴포넌트에서 서버 컴포넌트로 변경
원래는 모든 api 요청을 tmdb 서버 → 프로젝트 서버 → 프로젝트 서버에 클라이언트에서 api 요청하는 방식으로 처리함
search 페이지는 검색 이벤트 때문에 클라이언트 컴포넌트여야 하므로, 프로젝트 서버 이용(search를 위한 api 1개만 남김) 필요
반면, main 페이지는 클라이언트 컴포넌트일 필요 없음→ 클라이언트 측에서 tmdb 서버로 api 요청보내는 비동기 함수 utils 폴더 내에 구현해서 tmdb에 비동기 요청 직접 보냄
utils -> movieAPI에 fetch 함수 =이를 main 페이지에서 이용해서, 영화 정보 불러오기
클라이언트 컴포넌트인 search 페이지에서 api를 통해, 검색어에 따른 영화정보 불러올 수 있도록 구현
navbar 컴포넌트 공용 레이아웃으로 변경
utils로 API 관련 함수로 빼서 재사용
Image 레거시 코드 제거
트러블 슈팅
search 페이지에서, 4개 카테고리 영화들 flat()으로 합침 → removeDuplicates 함수(utils/etc.ts)에서 Map을 사용해 중복 제거
→ 검색 시, 동일 영화 중복해서 뜨는 문제 해결