Skip to content
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 TIG 송은수 & 김승완 미션 제출합니다. #11

Open
wants to merge 84 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
88bc203
First nextJS project setting
Programming-Seungwan May 6, 2024
cf9fdd2
chore: react-lottie-player 설치
songess May 7, 2024
a373fc3
style: globalCss 설정
songess May 7, 2024
7a8ec71
feat: Landing 컴포넌트 구현
songess May 7, 2024
bd96667
chore: @svgr/webpack 설치
songess May 7, 2024
068bbdb
feat: svg 환경 설정
songess May 7, 2024
b299253
feat: MainHeader 컴포넌트 생성
songess May 7, 2024
84cb2b0
Merge pull request #1 from CEOS-Frontend-Assignment-Eunsu-Seungwan/so…
Programming-Seungwan May 7, 2024
f56d09b
Fix : /components 라는 url path에 엉뚱한 것이 렌더링되지 않도록 디렉터리명 변경
Programming-Seungwan May 7, 2024
13b928e
Chore : 절대 경로 import 를 위한 tsconfig.json 설정 변경
Programming-Seungwan May 7, 2024
1da6b2c
Chore : /src/app/home 디렉터리를 /src/app/main 디렉터리로 이름 변경
Programming-Seungwan May 7, 2024
523c42e
Chore : main 페이지에서만 쓰이는 MainHeader를 components/main 디렉터리로 옮겨놓음
Programming-Seungwan May 7, 2024
4b4b550
Merge pull request #2 from CEOS-Frontend-Assignment-Eunsu-Seungwan/se…
songess May 8, 2024
ab062da
Refactor : prettier 관련 설정 파일과 tailwind.config.ts에서 사용하지 않는 클래스 정의 삭제
Programming-Seungwan May 8, 2024
872f1f7
Feat : main 경로와 / 경로에 해당하는 기본 레이아웃 만듦
Programming-Seungwan May 8, 2024
1d10817
Merge pull request #3 from CEOS-Frontend-Assignment-Eunsu-Seungwan/se…
songess May 8, 2024
652a054
fix: TabBar 컴포넌트 수정
songess May 8, 2024
c3f2129
feat: PreviewImage 컴포넌트 생성
songess May 9, 2024
a334762
Merge pull request #4 from CEOS-Frontend-Assignment-Eunsu-Seungwan/so…
Programming-Seungwan May 9, 2024
4e23b71
Feat : themovieAPI 연결
Programming-Seungwan May 9, 2024
dab718e
Fix : 이미지 렌더링 시 fetching되는 순서에 따라 드문드문 나타나는 현상 해결
Programming-Seungwan May 9, 2024
045f66d
Merge pull request #5 from CEOS-Frontend-Assignment-Eunsu-Seungwan/se…
songess May 9, 2024
3a17e45
feat: BackgroundImage 컴포넌트 구현
songess May 9, 2024
8a1a298
feat: MovieBar 컴포넌트 구현
songess May 9, 2024
0814f69
refactor: overflow 처리
songess May 9, 2024
063dd54
feat: CardSection 컴포넌트 구현
songess May 9, 2024
5ef2fff
refactor: 웹폰트 적용
songess May 9, 2024
ad7ae36
refactor: 페이지 자체가 옆으로 살짝 움직이는 현상 수정
songess May 9, 2024
b37d952
fix: CardSection 컴포넌트 padding 수정
songess May 9, 2024
f884877
Merge pull request #6 from CEOS-Frontend-Assignment-Eunsu-Seungwan/so…
Programming-Seungwan May 9, 2024
39c5d9e
Feat : tab bar 를 통한 라우팅 구현
Programming-Seungwan May 9, 2024
bdcfce5
Fix : tabBar 관련 svg 설정 css 수정
Programming-Seungwan May 9, 2024
a7dce15
Feat : 구현 필요한 페이지들에 대한 라우팅 진행
Programming-Seungwan May 9, 2024
c6aaef3
Merge pull request #7 from CEOS-Frontend-Assignment-Eunsu-Seungwan/se…
Programming-Seungwan May 9, 2024
1880f55
Fix : production 환경에서 BackgroundImage가 SSG 방식으로 인해 랜덤으로 적용되지 않는 문제 해결
Programming-Seungwan May 10, 2024
9ecb228
Feat : site meta 데이터 변경
Programming-Seungwan May 10, 2024
6fadb2b
Fix : next/image layout, object cover 속성 정상 적용
Programming-Seungwan May 10, 2024
bee57e9
Fix : add sizes and priority props to Image component
Programming-Seungwan May 10, 2024
9aff25f
Fix : add headers config in next.config.mjs file for production
Programming-Seungwan May 10, 2024
6d6652c
Fix : header 관련 설정 다시 삭제
Programming-Seungwan May 10, 2024
35d1c20
refactor: TabBar 리팩토링
songess May 10, 2024
be17546
Merge pull request #8 from CEOS-Frontend-Assignment-Eunsu-Seungwan/so…
Programming-Seungwan May 11, 2024
0bac994
Fix : TabItem ts error fix
Programming-Seungwan May 11, 2024
161d422
Fix : TabItem 컴포넌트의 key로 index가 아닌 tabBar.name을 부여
Programming-Seungwan May 11, 2024
2cf069b
Refactor : 공통 레이아웃 except for 랜딩 페이지를 group routing으로 묶어줌
Programming-Seungwan May 12, 2024
e88dc99
Refactor : 불필요한 import문 삭제
Programming-Seungwan May 12, 2024
328aaca
Refactor : 중복되는 Landing.tsx 컴포넌트의 삭제와 json을 가져오는 로직을 절대경로로 수정
Programming-Seungwan May 13, 2024
060ed1e
Chore : MovieBar의 여러 요소에 hover 시 cursor: pointer를 적용해주었음
Programming-Seungwan May 13, 2024
b55c06a
Feat : search 페이지에서 보여줄 top rated 영화 정보를 page 별로 보여줄 수 있는 함수와 영화 id를 …
Programming-Seungwan May 13, 2024
8f74a7e
Merge pull request #9 from CEOS-Frontend-Assignment-Eunsu-Seungwan/se…
songess May 14, 2024
e5f800a
refactor: getMovieNowPlaying.ts 파일위치 변경
songess May 14, 2024
b0c38d6
Merge pull request #10 from CEOS-Frontend-Assignment-Eunsu-Seungwan/s…
Programming-Seungwan May 14, 2024
b532e9f
refactor: 커스텀훅을 사용해 영화 이미지 가져오기 구현
songess May 14, 2024
4a58d1a
feat: useGetMovieImgAndTitle hook 생성
songess May 14, 2024
7cff32f
Feat : BackgroundImage, PreviewImage들을 누르면 해당하는 movieId에 해당하는 /movie-…
Programming-Seungwan May 14, 2024
0e2b938
feat: SearchInput 컴포넌트 구현
songess May 14, 2024
7a7b745
Merge pull request #11 from CEOS-Frontend-Assignment-Eunsu-Seungwan/s…
Programming-Seungwan May 14, 2024
d2e197f
Feat : make default movie-detail Layout and page
Programming-Seungwan May 14, 2024
39ea7e9
Merge branch 'seungwan' to master
Programming-Seungwan May 14, 2024
60b1f67
Merge pull request #12 from CEOS-Frontend-Assignment-Eunsu-Seungwan/s…
songess May 14, 2024
03d09f7
Feat : 첫 /movie-detail 페이지 완성
Programming-Seungwan May 14, 2024
e9b92d7
Fix : preview 데이터가 없을 경우 대체 텍스트를 보여줌 in /movie-detail 페이지에서
Programming-Seungwan May 14, 2024
38a7325
Merge pull request #13 from CEOS-Frontend-Assignment-Eunsu-Seungwan/s…
songess May 15, 2024
682725e
style: search 페이지 스타일링 변경
songess May 14, 2024
c201b64
feat: SearchMovieCard 클릭 시 상세페이지로 이동 구현
songess May 14, 2024
d9f0abb
refactor: movieInfoWithTitle 타입 성성
songess May 15, 2024
d59d959
chore: recoil 설치
songess May 15, 2024
d6670e1
feat: showingMovieAtom 생성 및 recoil 환경설정
songess May 15, 2024
fe5d9a9
feat: MovieSection 구현
songess May 15, 2024
94b6f80
feat: search 페이지 검색기능 구현
songess May 15, 2024
a375c98
feat: 무한스크롤 구현
songess May 15, 2024
c6df833
Merge pull request #14 from CEOS-Frontend-Assignment-Eunsu-Seungwan/s…
Programming-Seungwan May 15, 2024
223320b
Fix : Image 컴포넌트의 warning 관련 해결
Programming-Seungwan May 15, 2024
0977c2b
Fix : api key 노출 문제 해결
Programming-Seungwan May 16, 2024
fe17122
Merge pull request #15 from CEOS-Frontend-Assignment-Eunsu-Seungwan/s…
songess May 16, 2024
bcf5bcb
feat: 무한스크롤 SSG방식으로 구현
songess May 16, 2024
523273c
feat: 송은수 Readme.md 작성
songess May 16, 2024
d0ac3de
Merge pull request #16 from CEOS-Frontend-Assignment-Eunsu-Seungwan/s…
Programming-Seungwan May 16, 2024
a69e992
Docs : ceos 질문 문서 작성 by seungwan
Programming-Seungwan May 16, 2024
bd10262
fix: Search 페이지 검색기능 수정
songess May 17, 2024
31dc20a
Merge pull request #17 from CEOS-Frontend-Assignment-Eunsu-Seungwan/s…
Programming-Seungwan May 17, 2024
4514985
fix: 무한스크롤 API호출 SSR 방식으로 변경
songess May 18, 2024
6aa928a
Merge pull request #18 from CEOS-Frontend-Assignment-Eunsu-Seungwan/s…
Programming-Seungwan May 18, 2024
5ef5e66
Chore : 불필요한 콘솔 출력문 제거
Programming-Seungwan May 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
node_modules

.env*
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}
261 changes: 211 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,211 @@
# 5주차 미션: Next-Netflix

## 서론

안녕하세요, 프론트 운영진 **노이진**입니다 🐶🍮

이번주부터는 새 프로젝트인 **Netflix 클론코딩**을 진행합니다. 이번 미션은 Next.js를 사용해 보며 SSR을 학습하고 Figma로 주어지는 디자인을 활용해 스타일링 하는 방법을 이해하는 것을 목표로 합니다.

또한 이번주부터는 프론트 페어와 함께하는 과제인 만큼 각 팀별로 미리 호흡을 맞춰 보는 좋은 기회가 될 것 같습니다. 모두 화이팅입니다🔥

## 미션

### 미션 목표

- Next.js 사용법을 공부해봅니다.
- Figma로 주어지는 디자인으로 스타일링 하는 방식에 익숙해집니다.
- Git을 이용한 협업 방식에 익숙해집니다.

### 기한

- 2024년 5월 10일 (기한 엄수)

### 필수 요건

- [결과화면](https://next-netflix-18th-2.vercel.app/home)의 랜딩 페이지와 메인 페이지를 구현합니다.
- [Figma](https://www.figma.com/file/UqdXDovIczt1Gl0IjknHQf/Netflix?node-id=0%3A1)의 디자인을 그대로 구현합니다.
- Open api를 사용해서 데이터 패칭을 진행합니다. (ex. [themoviedb API](https://developers.themoviedb.org/3/getting-started/introduction))
- `yarn`, `yarn berry`, `npm`, `pnpm`등 패키지 매니저를 직접 선택해 Next.js를 세팅해 봅니다.

### 선택 사항

- SSR(Server Side Rendering)을 적용해서 구현합니다.
- 웹 폰트를 사용합니다.
- 반응형을 고려합니다.

## **Key Question**

- Server Side Rendering과 Client Side Rendering의 차이
- SEO란
- 전반적인 협업 과정

## 링크 및 참고자료

- [랜딩페이지 영상](https://lottiefiles.com/kr/)
- [Next.js Docs](https://beta.nextjs.org/docs)
- [Next.js 13에서 변한 것들](https://velog.io/@hang_kem_0531/Next.js-13%EC%9D%B4-%EB%82%98%EC%99%80%EB%B2%84%EB%A0%B8%EB%8B%A4)
- [Next.js 14에서 변한 것들](https://velog.io/@lee_1124/Next.js-14-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8)
- [Git 협업 가이드](https://velog.io/@jinuku/Git-%ED%98%91%EC%97%85-%EA%B0%80%EC%9D%B4%EB%93%9C)
- [디자이너와 개발자가 협업하기 위한 피그마 기본 기능](https://chingguhl.tistory.com/entry/%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EA%BC%AD-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%ED%94%BC%EA%B7%B8%EB%A7%88-10%EA%B0%80%EC%A7%80-%EA%B8%B0%EB%8A%A5-%EB%94%94%EC%9E%90%EC%9D%B4%EB%84%88%EC%99%80-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%ED%98%91%EC%97%85%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%ED%94%BC%EA%B7%B8%EB%A7%88-%EA%B8%B0%EB%B3%B8-%EA%B8%B0%EB%8A%A5)
- [React에서 무한 스크롤 구현하기](https://tech.kakaoenterprise.com/149)
# 6주차 미션: Next-Netflix

# 구현기능

1. detail 페이지 구현
2. search 페이지 구현
- 무한스크롤 구현
- 검색기능 구현

# 느낀점 및 시간투자 부분

### Recoil

1. 기본적인 `page`컴포넌트는 서버 컴포넌트로 만들고, 상호작용이 필요한 `SearchInput`,`MovieSection`컴포넌트만 클라이언트 컴포넌트로 구현하고 싶음.
2. 각각의 컴포넌트안에서 해결하여 최대한 드릴링 없이 컴포넌트를 만들고 싶음
두 가지 이슈를 해결하기 위해 `recoil`을 사용했다. `recoil`는 CSR에서 사용할 수 있어서 클라이언트 컴포넌트를 선언해줘야 했다. `app/layout.tsx`에서 `Recoil`을 도입하고 싶었는데 리액트처럼 그냥 컴포넌트 전체를 감싸버리면 오류가 발생했다.

```ts
// app/layout.tsx
// error
'use client';
...
export const metadata: Metadata = {
title: 'Graphy',
description: 'Project Share platform',
};
...
```

`Metadata`데이터가 정의되어있는 최상위컴포넌트는 서버 컴포넌트로 만들 수 없었다.

```ts
//RecoilRootProvider.tsx
'use client';

import { RecoilRoot } from 'recoil';
export default function RecoilRootProvider({
children,
}: {
children: React.ReactNode;
}) {
return <RecoilRoot>{children}</RecoilRoot>;
}

//app/layout.tsx
...
{
return (
<html lang="en">
<head>
<link rel="icon" href="/icon/netflix.ico" type="image/x-icon" />
</head>
<body className={inter.className}>
<RecoilRootProvider>{children}</RecoilRootProvider>
</body>
</html>
);
}
```

이를 해결하기 위해 `RecoilRootProvider`컴포넌트를 만들어 그 안에서 `recoil`을 선언하고 묶어주었다.

### 성능 최적화

```ts
// app/search/page.tsx
import MovieSection from '@components/search/MovieSection';
import SearchInput from '@components/search/SearchInput';

export default async function SearchPage() {
return (
<section className="w-full h-full flex flex-col gap-5">
<SearchInput />
<h1 className="text-[26px] leading-[20px] tracking-[-0.07px] p-[10px] font-bold">
Top Searches
</h1>
<MovieSection />
</section>
);
}
```

`Search`페이지에서 유저와의 상호작용이 필요한 것은 `input`박스와 `input`에 들어오는 인풋값에 따른 검색창이였다(무한스크롤을 위해서도). 성능 최적화를 위해 나머지부분은 서버단에서 처리하고 싶어서 `page`컴포넌트자체는 서버컴포넌트로 구현하고 `SearchInput`,`MovieSection`은 클라이언트 컴포넌트로 만들어줘 유저와의 상호작용이 가능하게 만들었다.

### 무한스크롤 구현

다른 라이브러리를 사용하지 않고 무한스크롤을 구현해보고 싶었다. Web API인 `IntersectionObserver`를 사용해 무한스크롤을 구현했다.

```ts
...
export default function MovieSection() {
const loaderRef = useRef<HTMLDivElement>(null);
const [isLoading, setIsLoading] = useState(false);
const [number, setNumber] = useState(2);
...

const loadMore = useCallback(async () => { // 데이터를 받아오는 함수
setIsLoading(true);
try { // 데이터 받아와서 추가하기
const res = await getMovieTopRatedByPageNumber(number);
const newMovies = res.map((movie: any) => ({
poster_path: movie.poster_path,
title: movie.title,
id: movie.id,
}));
setShowingMovies((prev) => [...prev, ...newMovies]);
setNumber((prev) => prev + 1);
} catch (error) {
console.error('Failed to load more movies', error);
} finally {
setIsLoading(false);
}
}, [number, setShowingMovies]);

useEffect(() => {
const observer = new IntersectionObserver( // API호출
(entries: IntersectionObserverEntry[]) => {
const firstEntry = entries[0];
if (firstEntry.isIntersecting && !isLoading) { // ref된 div태그가 화면에 렌더링되면 함수 실행
loadMore();
}
}
);
...
}, [isLoading, loadMore]);

return (
<div className="flex flex-col w-full overflow-scroll gap-1 mb-[86px]">
{showingMovies.map((movie, idx) => (
...
))}
<div ref={loaderRef}></div> //ref를 사용해 화면에 렌더링되는지 확인한다.
</div>
);
}
```

`MovieSection`내부에서 영화리스트들 태그아래 `ref`를 단 태그를 만들고, 유저가 스크롤하여 모든 영화리스트들을 확인하고 `ref`단 태그가 보이면 `useEffect`에서 `firstEntry.isIntersecting`속성을 통해 감지하고 `loadMore()`를 호출해 다음 페이지의 영화리스트들을 가져오는 형식으로 구현했다.

### 클라이언트 컴포넌트에서의 api key 노출 문제

`use client` directive를 쓰지 않는 서버 컴포넌트는 브라우저 단에서 data를 fetching 하는 로직이 실행되지 않고 배포 서버에서 진행되기에 api key가 노출되지 않는다. <br>
하지만 클라이언트 컴포넌트는 javascript가 브라우저 단에서 실행되기 때문에 api key를 브라우저 개발자 도구의 네트워크 탭을 보면 나타난다는 문제가 발생하고 있었다. 이를 해결하기 위해 생각한 방법은 다음과 같다.<br>
`next.config.js` 설정 파일의 `rewrites()` 함수를 이용하여 외부 url로의 api 호출을 **"마스킹"**하는 것이다.

```js
async rewrites() {
return [
{
source: '/api/nowPlayingmovies',
destination: `https://api.themoviedb.org/3/movie/popular?api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}`,
},
{
source: '/api/popularMovies',
destination: `https://api.themoviedb.org/3/movie/popular?api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}`,
},
{
source: `/api/topRatedMovies`,
destination: `https://api.themoviedb.org/3/movie/top_rated?api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}`,
},
{
source: `/api/topRatedMoviesByPage`,
has: [
{
type: 'query',
key: 'pageNumber',
value: '(?<pageNumber>.*)',
},
],
destination: `https://api.themoviedb.org/3/movie/top_rated?api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}&page=:pageNumber`,
},
{
source: '/api/upComingMovies',
destination: `https://api.themoviedb.org/3/movie/upcoming?api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}`,
},
{
source: '/api/movieInfo/:path*',
destination: `https://api.themoviedb.org/3/movie/:path*?language=en-US&api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}`,
},
];
},

```

<br>

위의 코드를 보면 여러 외부 url 엔드포인트로의 요청을 다른 url path로 숨겨주고 있다. 이렇게 작성한 로직은 `fetch()` 함수에서 사용되고 더 이상 api key가 노출되지 않는다는 것을 알 수 있다.

# Key Question

## 정적 라우팅(Static Routing)/동적 라우팅(Dynamic Routing)이란?

nextJS를 이용해 웹 애플리케이션을 만들 때, 고정된(정적인) url path를 가진 페이지가 있을 수도 있지만, 상황에 따라 달라지는 동적인 페이지가 있을 수도 있다.<br>
전자는 정적 라우팅, 후자는 동적 라우팅이라고 불린다.<br>
정적 라우팅의 경우에는 고정된 url path에 layout.tsx나 page.tsx 컴포넌트를 생성하기에 프로그래머가 이에 대응하는 값을 이용하여 웹 페이지를 렌더링할 수 있다. 하지만 동적 라우팅은 매번 세부 url path가 다르므로 이를 얻는 과정이 필요한데, 해당 컴포넌트가 클라이언트 컴포넌트 / 서버 컴포넌트인지에 따라 다르다.

- 클라이언트 컴포넌트 : `usePathname()` 훅과 객체 구조 분해 방식을 통해 이용할 수 있다.
- 서버 컴포넌트 : 자동으로 page.tsx 컴포넌트의 인자로 `params` 객체 속성이 전달된다. 동적으로 변화할 디렉터리 이름을 `[]`으로 감싸주고 해당하는 파일 이름을 변수명처럼 사용하면 된다. 디렉터리명을 `[...slug]`로 지어주면 /shop/a의 경우에는 ['a'], /shop/a/b 의 경우에는 ['a', 'b']처럼 사용할 수 있다.
<br><br>

주목해서 볼만한 점은, 동적으로 변화하는 url path를 가지는 페이지의 경우에도 `getStaticParams()` 함수를 이용하여 미리 SSG(Static Site Generation) 방식으로 렌더링하여 사용자에게 로드되는 속도를 개선할 수 있다는 것이다.

## 성능 최적화를 위해 사용한 방법

1. `next/image`에서 제공되는 <Image/>를 사용했다.
<Image />를 써서 얻을 수 있는 이점
- lazy loading: 이미지 로드를 필요한 시점까지 지연시켜 불필요한 대역폭을 줄이고 필요한 이미지만 빠르게 로딩할 수 있게 해준다.
- 이미지 사이즈 최적화: 디바이스 크기 별로 srcSet을 미리 지정해두고, 사용자의 디바이스에 맞는 이미지를 다운로드할 수 있게 지원한다. 또한 이미지를 webp와 같은 용량이 작은 포맷으로 이미지를 변환해서 제공한다. 개발자도구 네트워크에서 API `GET`요청에 따른 결과값을 확인해보면 `jpg`이지만 화면에 렌더링되어있는 이미지를 확인해보면 `webp`인걸 확인할 수 있다.
- placeholder 제공: 이미지없이 `paint`가 되었다가 이미지가 로드되면 `repaint`되면 화면이 밀리는 현상을 방지하기 위해 `placeholder`속성을 넣어줄 수 있다. 하지만 이는 `src`의 경로가 `jpg,png`등 정적일 때만 가능하다. 따라서 이 기능을 대체하기 위해 `sharp`라이브러리를 통해 해결했다.
2. 유저와 상호작용 필요한 컴포넌트가 아니면 서버컴포넌트를 사용하여 만들었다. 시간투자부분에서 설명한 것 처럼 상호작용이 필요한 컴포넌트에서도 상호작용이 필요하지 않은 부분은 서버단에서 실행되어 받을 수 있도록 구현했다.
3. 무한 스크롤을 구현할 때 영화리스트 가장 밑에부분에 도달하면 API호출을 했다. 무한스크롤에서 버벅이는 부분이 최대한 보이지 않도록 해당 API 호출을 SSG방식으로 만들어 미리 20페이지까지 빌드타임에 호출했다. 이후 스크롤을 내리며 다음 페이지 API호출이 필요할 때 미리 받아온 API를 사용할 수 있게 구현했다.
5 changes: 5 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module "*.svg" {
import React from "react";
const svg: React.FC<React.SVGProps<SVGSVGElement>>;
export default svg;
}
61 changes: 61 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ['image.tmdb.org'],
},
webpack: (config) => {
config.module.rules.push({
test: /\.svg$/i,
use: ['@svgr/webpack'],
});

return config;
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'image.tmdb.org',
port: '',
pathname: '/t/p/**',
},
],
},
async rewrites() {
return [
{
source: '/api/nowPlayingmovies',
destination: `https://api.themoviedb.org/3/movie/popular?api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}`,
},
{
source: '/api/popularMovies',
destination: `https://api.themoviedb.org/3/movie/popular?api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}`,
},
{
source: `/api/topRatedMovies`,
destination: `https://api.themoviedb.org/3/movie/top_rated?api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}`,
},
{
source: `/api/topRatedMoviesByPage`,
has: [
{
type: 'query',
key: 'pageNumber',
value: '(?<pageNumber>.*)',
},
],
destination: `https://api.themoviedb.org/3/movie/top_rated?api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}&page=:pageNumber`,
},
{
source: '/api/upComingMovies',
destination: `https://api.themoviedb.org/3/movie/upcoming?api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}`,
},
{
source: '/api/movieInfo/:path*',
destination: `https://api.themoviedb.org/3/movie/:path*?language=en-US&api_key=${process.env.NEXT_PUBLIC_THEMOVIE_API_KEY}`,
},
];
},
};

export default nextConfig;
Loading